diff --git a/.travis.yml b/.travis.yml index 9973c293..f6a2a31f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,10 @@ language: php php: - - '5.5' - - '5.6' - - '7.0' - - '7.1' - - '7.2' + - '7.3' + - '7.4' env: - - MYSQL_VERSION=5.7 - MYSQL_VERSION=8.0 dist: trusty @@ -19,7 +15,7 @@ services: - docker before_install: - - echo "memory_limit=2G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - echo "memory_limit=3G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - sudo /etc/init.d/mysql stop - make start_db V=$MYSQL_VERSION @@ -33,7 +29,5 @@ before_script: script: vendor/bin/phpunit --coverage-clover build/logs/clover.xml -after_script: - - php vendor/bin/coveralls -v - - ./cc-test-reporter after-build --coverage-input-type clover --exit-code $TRAVIS_TEST_RESULT +after_script: ./cc-test-reporter after-build --coverage-input-type clover --exit-code $TRAVIS_TEST_RESULT diff --git a/Makefile b/Makefile index cb716a28..62db4599 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -V=5.7 +V=8.0 DB_DIR=$(shell pwd)/_db-$(V) mV=10.3 mDB_DIR=$(shell pwd)/_db-$(mV) diff --git a/README.md b/README.md index 58a58de2..39d5303c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,10 @@ Please check the documentation for your MySQL version. MySQL's Extension for Spa **Versions** - `1.x.x`: MySQL 5.6 (also supports MySQL 5.5 but not all spatial analysis functions) -- `2.x.x`: MySQL 5.7 and 8.0 +- `2.x.x`: MySQL 5.7 and 8.0 (Laravel version < 8.0) +- `3.x.x`: MySQL 8.0 with SRID support (Laravel version < 8.0) +- **`4.x.x`: MySQL 8.0 with SRID support (Laravel 8+) [Current branch]** +- `5.x.x`: MySQL 5.7 and 8.0 (Laravel 8+) This package also works with MariaDB. Please refer to the [MySQL/MariaDB Spatial Support Matrix](https://mariadb.com/kb/en/library/mysqlmariadb-spatial-support-matrix/) for compatibility. @@ -21,14 +24,23 @@ This package also works with MariaDB. Please refer to the [MySQL/MariaDB Spatial Add the package using composer: +```sh +$ composer require grimzy/laravel-mysql-spatial:^4.0 + +# or for Laravel version < 8.0 +$ composer require grimzy/laravel-mysql-spatial:^3.0 +``` + +For MySQL 5.7: + ```shell -composer require grimzy/laravel-mysql-spatial +$ composer require grimzy/laravel-mysql-spatial:^2.0 ``` For MySQL 5.6 and 5.5: ```shell -composer require grimzy/laravel-mysql-spatial:^1.0 +$ composer require grimzy/laravel-mysql-spatial:^1.0 ``` For Laravel versions before 5.5 or if not using auto-discovery, register the service provider in `config/app.php`: @@ -80,6 +92,19 @@ class CreatePlacesTable extends Migration { $table->polygon('area')->nullable(); $table->timestamps(); }); + + // Or create the spatial fields with an SRID (e.g. 4326 WGS84 spheroid) + + // Schema::create('places', function(Blueprint $table) + // { + // $table->increments('id'); + // $table->string('name')->unique(); + // // Add a Point spatial data field named location with SRID 4326 + // $table->point('location', 4326)->nullable(); + // // Add a Polygon spatial data field named area with SRID 4326 + // $table->polygon('area', 4326)->nullable(); + // $table->timestamps(); + // }); } /** @@ -140,6 +165,7 @@ class Place extends Model ```php use Grimzy\LaravelMysqlSpatial\Types\Point; use Grimzy\LaravelMysqlSpatial\Types\Polygon; +use Grimzy\LaravelMysqlSpatial\Types\LineString; $place1 = new Place(); $place1->name = 'Empire State Building'; @@ -157,11 +183,37 @@ $place1->area = new Polygon([new LineString([ new Point(40.74894149554006, -73.98615270853043) ])]); $place1->save(); +``` + +Or if your database fields were created with a specific SRID: + +```php +use Grimzy\LaravelMysqlSpatial\Types\Point; +use Grimzy\LaravelMysqlSpatial\Types\Polygon; +use Grimzy\LaravelMysqlSpatial\Types\LineString; + +$place1 = new Place(); +$place1->name = 'Empire State Building'; -$place1->area = new Polygon(); +// saving a point with SRID 4326 (WGS84 spheroid) +$place1->location = new Point(40.7484404, -73.9878441, 4326); // (lat, lng, srid) +$place1->save(); +// saving a polygon with SRID 4326 (WGS84 spheroid) +$place1->area = new Polygon([new LineString([ + new Point(40.74894149554006, -73.98615270853043), + new Point(40.74848633046773, -73.98648262023926), + new Point(40.747925497790725, -73.9851602911949), + new Point(40.74837050671544, -73.98482501506805), + new Point(40.74894149554006, -73.98615270853043) +])], 4326); +$place1->save(); ``` +> **Note**: When saving collection Geometries (`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, and `GeometryCollection`), only the top-most geometry should have an SRID set in the constructor. +> +> In the example above, when creating a `new Polygon()`, we only set the SRID on the `Polygon` and use the default for the `LineString` and the `Point` objects. + ### Retrieving a model ```php @@ -176,13 +228,13 @@ $lng = $place2->location->getLng(); // -73.9878441 | Grimzy\LaravelMysqlSpatial\Types | OpenGIS Class | | ------------------------------------------------------------ | ------------------------------------------------------------ | -| `Point($lat, $lng)` | [Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) | -| `MultiPoint(Point[])` | [MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) | -| `LineString(Point[])` | [LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) | -| `MultiLineString(LineString[])` | [MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) | -| `Polygon(LineString[])` *([exterior and interior boundaries](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html))* | [Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html) | -| `MultiPolygon(Polygon[])` | [MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) | -| `GeometryCollection(Geometry[])` | [GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html) | +| `Point($lat, $lng, $srid = 0)` | [Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html) | +| `MultiPoint(Point[], $srid = 0)` | [MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html) | +| `LineString(Point[], $srid = 0)` | [LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html) | +| `MultiLineString(LineString[], $srid = 0)` | [MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html) | +| `Polygon(LineString[], $srid = 0)` *([exterior and interior boundaries](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html))* | [Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html) | +| `MultiPolygon(Polygon[], $srid = 0)` | [MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html) | +| `GeometryCollection(Geometry[], $srid = 0)` | [GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html) | Check out the [Class diagram](https://user-images.githubusercontent.com/1837678/30788608-a5afd894-a16c-11e7-9a51-0a08b331d4c4.png). @@ -192,7 +244,7 @@ In order for your Eloquent Model to handle the Geometry classes, it must use the #### IteratorAggregate and ArrayAccess -The "composite" Geometries (`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, and `GeometryCollection`) implement [`IteratorAggregate`](http://php.net/manual/en/class.iteratoraggregate.php) and [`ArrayAccess`](http://php.net/manual/en/class.arrayaccess.php); making it easy to perform Iterator and Array operations. For example: +The collection Geometries (`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, and `GeometryCollection`) implement [`IteratorAggregate`](http://php.net/manual/en/class.iteratoraggregate.php) and [`ArrayAccess`](http://php.net/manual/en/class.arrayaccess.php); making it easy to perform Iterator and Array operations. For example: ```php $polygon = $multipolygon[10]; // ArrayAccess @@ -206,10 +258,10 @@ for($polygon as $i => $linestring) { #### Helpers -##### From/To Well Known Text ([WKT](https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkt-format)) +##### From/To Well Known Text ([WKT](https://dev.mysql.com/doc/refman/8.0/en/gis-data-formats.html#gis-wkt-format)) ```php -// fromWKT($wkt) +// fromWKT($wkt, $srid = 0) $point = Point::fromWKT('POINT(2 1)'); $point->toWKT(); // POINT(2 1) @@ -220,9 +272,9 @@ $polygon->toWKT(); // POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) ##### From/To String ```php -// fromString($wkt) +// fromString($wkt, $srid = 0) $point = new Point(1, 2); // lat, lng -(string)$point // lng, lat: 2 1 +(string)$point // lng, lat: 2 1 $polygon = Polygon::fromString('(0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)'); (string)$polygon; // (0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1) @@ -254,9 +306,9 @@ To deserialize a GeoJSON string into a Geometry class, you can use `Geometry::fr ```php $location = Geometry::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); -$location instanceof Point::class; // true -$location->getLat(); // 1.2 -$location->getLng()); // 3.4 +$location instanceof Point::class; // true +$location->getLat(); // 1.2 +$location->getLng()); // 3.4 ``` ## Scopes: Spatial analysis functions @@ -279,10 +331,10 @@ Available scopes: - `overlaps($geometryColumn, $geometry)` - `doesTouch($geometryColumn, $geometry)` - `orderBySpatial($geometryColumn, $geometry, $orderFunction, $direction = 'asc')` -- `orderByDistance($geometryColumn, ​$geometry, ​$direction = 'asc')` -- `orderByDistanceSphere($geometryColumn, ​$geometry, ​$direction = 'asc')` +- `orderByDistance($geometryColumn, $geometry, $direction = 'asc')` +- `orderByDistanceSphere($geometryColumn, $geometry, $direction = 'asc')` -*Note that behavior and availability of MySQL spatial analysis functions differs in each MySQL version (cf. [documentation](https://dev.mysql.com/doc/refman/5.7/en/spatial-function-reference.html)).* +*Note that behavior and availability of MySQL spatial analysis functions differs in each MySQL version (cf. [documentation](https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html)).* ## Migrations @@ -299,16 +351,16 @@ class CreatePlacesTable extends Migration { ### Columns -Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/5.7/en/spatial-datatypes.html) migration blueprints: +Available [MySQL Spatial Types](https://dev.mysql.com/doc/refman/8.0/en/spatial-type-overview.html) migration blueprints: -- `$table->geometry('column_name')` -- `$table->point('column_name')` -- `$table->lineString('column_name')` -- `$table->polygon('column_name')` -- `$table->multiPoint('column_name')` -- `$table->multiLineString('column_name')` -- `$table->multiPolygon('column_name')` -- `$table->geometryCollection('column_name')` +- `$table->geometry(string $column_name, int $srid = 0)` +- `$table->point(string $column_name, int $srid = 0)` +- `$table->lineString(string $column_name, int $srid = 0)` +- `$table->polygon(string $column_name, int $srid = 0)` +- `$table->multiPoint(string $column_name, int $srid = 0)` +- `$table->multiLineString(string $column_name, int $srid = 0)` +- `$table->multiPolygon(string $column_name, int $srid = 0)` +- `$table->geometryCollection(string $column_name, int $srid = 0)` ### Spatial indexes @@ -317,9 +369,9 @@ You can add or drop spatial indexes in your migrations with the `spatialIndex` a - `$table->spatialIndex('column_name')` - `$table->dropSpatialIndex(['column_name'])` or `$table->dropSpatialIndex('index_name')` -Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html): +Note about spatial indexes from the [MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/creating-spatial-indexes.html): -> For [`MyISAM`](https://dev.mysql.com/doc/refman/5.7/en/myisam-storage-engine.html) and (as of MySQL 5.7.5) `InnoDB` tables, MySQL can create spatial indexes using syntax similar to that for creating regular indexes, but using the `SPATIAL` keyword. Columns in spatial indexes must be declared `NOT NULL`. +> For [`MyISAM`](https://dev.mysql.com/doc/refman/8.0/en/myisam-storage-engine.html) and (as of MySQL 5.7.5) `InnoDB` tables, MySQL can create spatial indexes using syntax similar to that for creating regular indexes, but using the `SPATIAL` keyword. Columns in spatial indexes must be declared `NOT NULL`. Also please read this [**important note**](https://laravel.com/docs/5.5/migrations#indexes) regarding Index Lengths in the Laravel 5.6 documentation. @@ -380,18 +432,18 @@ class UpdatePlacesTable extends Migration ## Tests ```shell -composer test +$ composer test # or -composer test:unit -composer test:integration +$ composer test:unit +$ composer test:integration ``` Integration tests require a running MySQL database. If you have Docker installed, you can start easily start one: ```shell -make start_db # starts MySQL 8.0 +$ make start_db # starts MySQL 8.0 # or -make start_db V=5.7 # starts a MySQL 5.7 +$ make start_db V=5.7 # starts MySQL 5.7 ``` ## Contributing diff --git a/composer.json b/composer.json index 3c22d1c2..4451cb50 100644 --- a/composer.json +++ b/composer.json @@ -15,20 +15,19 @@ } ], "require": { - "php": ">=5.5", + "php": ">=7.3", "ext-pdo": "*", "ext-json": "*", - "illuminate/database": "^5.2|^6.0", + "illuminate/database": "^8.0", "geo-io/wkb-parser": "^1.0", "jmikola/geojson": "^1.0" }, "require-dev": { - "phpunit/phpunit": "~4.8||~5.7", - "mockery/mockery": "^0.9.9", - "laravel/laravel": "^5.2|^6.0", + "phpunit/phpunit": "~6.5", + "laravel/laravel": "^8.0", "doctrine/dbal": "^2.5", "laravel/browser-kit-testing": "^2.0", - "php-coveralls/php-coveralls": "^2.0" + "mockery/mockery": "^1.3" }, "autoload": { "psr-4": { @@ -43,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.0.x-dev" }, "laravel": { "providers": [ diff --git a/src/Eloquent/BaseBuilder.php b/src/Eloquent/BaseBuilder.php index 33c0f21c..a2130840 100644 --- a/src/Eloquent/BaseBuilder.php +++ b/src/Eloquent/BaseBuilder.php @@ -6,12 +6,18 @@ class BaseBuilder extends QueryBuilder { - protected function cleanBindings(array $bindings) + public function cleanBindings(array $bindings) { - $bindings = array_map(function ($binding) { - return $binding instanceof SpatialExpression ? $binding->getSpatialValue() : $binding; - }, $bindings); + $spatialBindings = []; + foreach ($bindings as &$binding) { + if ($binding instanceof SpatialExpression) { + $spatialBindings[] = $binding->getSpatialValue(); + $spatialBindings[] = $binding->getSrid(); + } else { + $spatialBindings[] = $binding; + } + } - return parent::cleanBindings($bindings); + return parent::cleanBindings($spatialBindings); } } diff --git a/src/Eloquent/SpatialExpression.php b/src/Eloquent/SpatialExpression.php index 7bc88178..9224af0f 100644 --- a/src/Eloquent/SpatialExpression.php +++ b/src/Eloquent/SpatialExpression.php @@ -8,11 +8,16 @@ class SpatialExpression extends Expression { public function getValue() { - return 'ST_GeomFromText(?)'; + return "ST_GeomFromText(?, ?, 'axis-order=long-lat')"; } public function getSpatialValue() { return $this->value->toWkt(); } + + public function getSrid() + { + return $this->value->getSrid(); + } } diff --git a/src/Eloquent/SpatialTrait.php b/src/Eloquent/SpatialTrait.php index c1bf101c..5cc3f4b1 100755 --- a/src/Eloquent/SpatialTrait.php +++ b/src/Eloquent/SpatialTrait.php @@ -75,7 +75,9 @@ protected function newBaseQueryBuilder() $connection = $this->getConnection(); return new BaseBuilder( - $connection, $connection->getQueryGrammar(), $connection->getPostProcessor() + $connection, + $connection->getQueryGrammar(), + $connection->getPostProcessor() ); } @@ -102,7 +104,7 @@ public function setRawAttributes(array $attributes, $sync = false) $spatial_fields = $this->getSpatialFields(); foreach ($attributes as $attribute => &$value) { - if (in_array($attribute, $spatial_fields) && is_string($value) && strlen($value) >= 15) { + if (in_array($attribute, $spatial_fields) && is_string($value) && strlen($value) >= 13) { $value = Geometry::fromWKB($value); } } @@ -132,8 +134,9 @@ public function scopeDistance($query, $geometryColumn, $geometry, $distance) { $this->isColumnAllowed($geometryColumn); - $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) <= ?", [ + $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) <= ?", [ $geometry->toWkt(), + $geometry->getSrid(), $distance, ]); @@ -146,8 +149,9 @@ public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $ $query = $this->scopeDistance($query, $geometryColumn, $geometry, $distance); - $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) != 0", [ + $query->whereRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) != 0", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; @@ -163,8 +167,9 @@ public function scopeDistanceValue($query, $geometryColumn, $geometry) $query->select('*'); } - $query->selectRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) as distance", [ + $query->selectRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) as distance", [ $geometry->toWkt(), + $geometry->getSrid(), ]); } @@ -172,8 +177,9 @@ public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distanc { $this->isColumnAllowed($geometryColumn); - $query->whereRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?)) <= ?", [ + $query->whereRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) <= ?", [ $geometry->toWkt(), + $geometry->getSrid(), $distance, ]); @@ -186,8 +192,9 @@ public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geome $query = $this->scopeDistanceSphere($query, $geometryColumn, $geometry, $distance); - $query->whereRaw("st_distance_sphere($geometryColumn, ST_GeomFromText(?)) != 0", [ + $query->whereRaw("st_distance_sphere($geometryColumn, ST_GeomFromText(?, ?, 'axis-order=long-lat')) != 0", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; @@ -202,8 +209,9 @@ public function scopeDistanceSphereValue($query, $geometryColumn, $geometry) if (!$columns) { $query->select('*'); } - $query->selectRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?)) as distance", [ + $query->selectRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) as distance", [ $geometry->toWkt(), + $geometry->getSrid(), ]); } @@ -215,8 +223,9 @@ public function scopeComparison($query, $geometryColumn, $geometry, $relationshi throw new UnknownSpatialRelationFunction($relationship); } - $query->whereRaw("st_{$relationship}(`$geometryColumn`, ST_GeomFromText(?))", [ + $query->whereRaw("st_{$relationship}(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat'))", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; @@ -270,8 +279,9 @@ public function scopeOrderBySpatial($query, $geometryColumn, $geometry, $orderFu throw new UnknownSpatialFunctionException($orderFunction); } - $query->orderByRaw("st_{$orderFunction}(`$geometryColumn`, ST_GeomFromText(?)) {$direction}", [ + $query->orderByRaw("st_{$orderFunction}(`$geometryColumn`, ST_GeomFromText(?, ?, 'axis-order=long-lat')) {$direction}", [ $geometry->toWkt(), + $geometry->getSrid(), ]); return $query; diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index d061da26..0a333f06 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -9,13 +9,14 @@ class Blueprint extends IlluminateBlueprint /** * Add a geometry column on the table. * - * @param string $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function geometry($column) + public function geometry($column, $srid = null) { - return $this->addColumn('geometry', $column); + return $this->addColumn('geometry', $column, compact('srid')); } /** @@ -34,73 +35,79 @@ public function point($column, $srid = null) /** * Add a linestring column on the table. * - * @param $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function lineString($column) + public function lineString($column, $srid = null) { - return $this->addColumn('linestring', $column); + return $this->addColumn('linestring', $column, compact('srid')); } /** * Add a polygon column on the table. * - * @param $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function polygon($column) + public function polygon($column, $srid = null) { - return $this->addColumn('polygon', $column); + return $this->addColumn('polygon', $column, compact('srid')); } /** * Add a multipoint column on the table. * - * @param $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function multiPoint($column) + public function multiPoint($column, $srid = null) { - return $this->addColumn('multipoint', $column); + return $this->addColumn('multipoint', $column, compact('srid')); } /** * Add a multilinestring column on the table. * - * @param $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function multiLineString($column) + public function multiLineString($column, $srid = null) { - return $this->addColumn('multilinestring', $column); + return $this->addColumn('multilinestring', $column, compact('srid')); } /** * Add a multipolygon column on the table. * - * @param $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function multiPolygon($column) + public function multiPolygon($column, $srid = null) { - return $this->addColumn('multipolygon', $column); + return $this->addColumn('multipolygon', $column, compact('srid')); } /** * Add a geometrycollection column on the table. * - * @param $column + * @param string $column + * @param null|int $srid * * @return \Illuminate\Support\Fluent */ - public function geometryCollection($column) + public function geometryCollection($column, $srid = null) { - return $this->addColumn('geometrycollection', $column); + return $this->addColumn('geometrycollection', $column, compact('srid')); } /** diff --git a/src/Schema/Grammars/MySqlGrammar.php b/src/Schema/Grammars/MySqlGrammar.php index 61f015d1..9afe4513 100644 --- a/src/Schema/Grammars/MySqlGrammar.php +++ b/src/Schema/Grammars/MySqlGrammar.php @@ -8,10 +8,20 @@ class MySqlGrammar extends IlluminateMySqlGrammar { + const COLUMN_MODIFIER_SRID = 'Srid'; + + public function __construct() + { + // Enable SRID as a column modifier + if (!in_array(self::COLUMN_MODIFIER_SRID, $this->modifiers)) { + $this->modifiers[] = self::COLUMN_MODIFIER_SRID; + } + } + /** * Adds a statement to add a geometry column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -23,7 +33,7 @@ public function typeGeometry(Fluent $column) /** * Adds a statement to add a point column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -35,7 +45,7 @@ public function typePoint(Fluent $column) /** * Adds a statement to add a linestring column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -47,7 +57,7 @@ public function typeLinestring(Fluent $column) /** * Adds a statement to add a polygon column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -59,7 +69,7 @@ public function typePolygon(Fluent $column) /** * Adds a statement to add a multipoint column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -71,7 +81,7 @@ public function typeMultipoint(Fluent $column) /** * Adds a statement to add a multilinestring column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -83,7 +93,7 @@ public function typeMultilinestring(Fluent $column) /** * Adds a statement to add a multipolygon column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -95,7 +105,7 @@ public function typeMultipolygon(Fluent $column) /** * Adds a statement to add a geometrycollection column. * - * @param \Illuminate\Support\Fluent $column + * @param Fluent $column * * @return string */ @@ -107,8 +117,8 @@ public function typeGeometrycollection(Fluent $column) /** * Compile a spatial index key command. * - * @param \Grimzy\LaravelMysqlSpatial\Schema\Blueprint $blueprint - * @param \Illuminate\Support\Fluent $command + * @param Blueprint $blueprint + * @param Fluent $command * * @return string */ @@ -116,4 +126,19 @@ public function compileSpatial(Blueprint $blueprint, Fluent $command) { return $this->compileKey($blueprint, $command, 'spatial'); } + + /** + * Get the SQL for a SRID column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param Fluent $column + * + * @return string|null + */ + protected function modifySrid(\Illuminate\Database\Schema\Blueprint $blueprint, Fluent $column) + { + if (!is_null($column->srid) && is_int($column->srid) && $column->srid > 0) { + return ' srid '.$column->srid; + } + } } diff --git a/src/Types/Factory.php b/src/Types/Factory.php index 087348fa..ed04ac2d 100755 --- a/src/Types/Factory.php +++ b/src/Types/Factory.php @@ -6,41 +6,41 @@ class Factory implements \GeoIO\Factory { public function createPoint($dimension, array $coordinates, $srid = null) { - return new Point($coordinates['y'], $coordinates['x']); + return new Point($coordinates['y'], $coordinates['x'], $srid); } public function createLineString($dimension, array $points, $srid = null) { - return new LineString($points); + return new LineString($points, $srid); } public function createLinearRing($dimension, array $points, $srid = null) { - return new LineString($points); + return new LineString($points, $srid); } public function createPolygon($dimension, array $lineStrings, $srid = null) { - return new Polygon($lineStrings); + return new Polygon($lineStrings, $srid); } public function createMultiPoint($dimension, array $points, $srid = null) { - return new MultiPoint($points); + return new MultiPoint($points, $srid); } public function createMultiLineString($dimension, array $lineStrings, $srid = null) { - return new MultiLineString($lineStrings); + return new MultiLineString($lineStrings, $srid); } public function createMultiPolygon($dimension, array $polygons, $srid = null) { - return new MultiPolygon($polygons); + return new MultiPolygon($polygons, $srid); } public function createGeometryCollection($dimension, array $geometries, $srid = null) { - return new GeometryCollection($geometries); + return new GeometryCollection($geometries, $srid); } } diff --git a/src/Types/Geometry.php b/src/Types/Geometry.php index c0df8ec3..f840874c 100644 --- a/src/Types/Geometry.php +++ b/src/Types/Geometry.php @@ -19,6 +19,23 @@ abstract class Geometry implements GeometryInterface, Jsonable, \JsonSerializabl 7 => GeometryCollection::class, ]; + protected $srid; + + public function __construct($srid = 0) + { + $this->srid = (int) $srid; + } + + public function getSrid() + { + return $this->srid; + } + + public function setSrid($srid) + { + $this->srid = (int) $srid; + } + public static function getWKTArgument($value) { $left = strpos($value, '('); @@ -54,18 +71,27 @@ public static function getWKTClass($value) public static function fromWKB($wkb) { - // mysql adds 4 NUL bytes at the start of the binary + $srid = substr($wkb, 0, 4); + $srid = unpack('L', $srid)[1]; + $wkb = substr($wkb, 4); $parser = new Parser(new Factory()); - return $parser->parse($wkb); + /** @var Geometry $parsed */ + $parsed = $parser->parse($wkb); + + if ($srid > 0) { + $parsed->setSrid($srid); + } + + return $parsed; } - public static function fromWKT($wkt) + public static function fromWKT($wkt, $srid = null) { $wktArgument = static::getWKTArgument($wkt); - return static::fromString($wktArgument); + return static::fromString($wktArgument, $srid); } public static function fromJson($geoJson) diff --git a/src/Types/GeometryCollection.php b/src/Types/GeometryCollection.php index 2e1f0321..35f093f7 100755 --- a/src/Types/GeometryCollection.php +++ b/src/Types/GeometryCollection.php @@ -14,6 +14,20 @@ class GeometryCollection extends Geometry implements IteratorAggregate, ArrayAccess, Arrayable, Countable { + /** + * The minimum number of items required to create this collection. + * + * @var int + */ + protected $minimumCollectionItems = 0; + + /** + * The class of the items in the collection. + * + * @var string + */ + protected $collectionItemType = GeometryInterface::class; + /** * The items contained in the spatial collection. * @@ -23,18 +37,15 @@ class GeometryCollection extends Geometry implements IteratorAggregate, ArrayAcc /** * @param GeometryInterface[] $geometries + * @param int $srid * * @throws InvalidArgumentException */ - public function __construct(array $geometries) + public function __construct(array $geometries, $srid = 0) { - $validated = array_filter($geometries, function ($value) { - return $value instanceof GeometryInterface; - }); + parent::__construct($srid); - if (count($geometries) !== count($validated)) { - throw new InvalidArgumentException('$geometries must be an array of Geometry objects'); - } + $this->validateItems($geometries); $this->items = $geometries; } @@ -56,15 +67,19 @@ public function __toString() }, $this->items)); } - public static function fromString($wktArgument) + public static function fromString($wktArgument, $srid = 0) { + if (empty($wktArgument)) { + return new static([]); + } + $geometry_strings = preg_split('/,\s*(?=[A-Za-z])/', $wktArgument); return new static(array_map(function ($geometry_string) { $klass = Geometry::getWKTClass($geometry_string); return call_user_func($klass.'::fromWKT', $geometry_string); - }, $geometry_strings)); + }, $geometry_strings), $srid); } public function toArray() @@ -89,9 +104,7 @@ public function offsetGet($offset) public function offsetSet($offset, $value) { - if (!($value instanceof GeometryInterface)) { - throw new InvalidArgumentException('$value must be an instance of GeometryInterface'); - } + $this->validateItemType($value); if (is_null($offset)) { $this->items[] = $value; @@ -142,4 +155,57 @@ public function jsonSerialize() return new \GeoJson\Geometry\GeometryCollection($geometries); } + + /** + * Checks whether the items are valid to create this collection. + * + * @param array $items + */ + protected function validateItems(array $items) + { + $this->validateItemCount($items); + + foreach ($items as $item) { + $this->validateItemType($item); + } + } + + /** + * Checks whether the array has enough items to generate a valid WKT. + * + * @param array $items + * + * @see $minimumCollectionItems + */ + protected function validateItemCount(array $items) + { + if (count($items) < $this->minimumCollectionItems) { + $entries = $this->minimumCollectionItems === 1 ? 'entry' : 'entries'; + + throw new InvalidArgumentException(sprintf( + '%s must contain at least %d %s', + get_class($this), + $this->minimumCollectionItems, + $entries + )); + } + } + + /** + * Checks the type of the items in the array. + * + * @param $item + * + * @see $collectionItemType + */ + protected function validateItemType($item) + { + if (!$item instanceof $this->collectionItemType) { + throw new InvalidArgumentException(sprintf( + '%s must be a collection of %s', + get_class($this), + $this->collectionItemType + )); + } + } } diff --git a/src/Types/GeometryInterface.php b/src/Types/GeometryInterface.php index aca2bfb0..4f0dd1ef 100644 --- a/src/Types/GeometryInterface.php +++ b/src/Types/GeometryInterface.php @@ -6,11 +6,11 @@ interface GeometryInterface { public function toWKT(); - public static function fromWKT($wkt); + public static function fromWKT($wkt, $srid = 0); public function __toString(); - public static function fromString($wktArgument); + public static function fromString($wktArgument, $srid = 0); public static function fromJson($geoJson); } diff --git a/src/Types/LineString.php b/src/Types/LineString.php index 02097edd..1cc4a410 100644 --- a/src/Types/LineString.php +++ b/src/Types/LineString.php @@ -8,26 +8,33 @@ class LineString extends PointCollection { + /** + * The minimum number of items required to create this collection. + * + * @var int + */ + protected $minimumCollectionItems = 2; + public function toWKT() { return sprintf('LINESTRING(%s)', $this->toPairList()); } - public static function fromWkt($wkt) + public static function fromWkt($wkt, $srid = 0) { $wktArgument = Geometry::getWKTArgument($wkt); - return static::fromString($wktArgument); + return static::fromString($wktArgument, $srid); } - public static function fromString($wktArgument) + public static function fromString($wktArgument, $srid = 0) { $pairs = explode(',', trim($wktArgument)); $points = array_map(function ($pair) { return Point::fromPair($pair); }, $pairs); - return new static($points); + return new static($points, $srid); } public function __toString() diff --git a/src/Types/MultiLineString.php b/src/Types/MultiLineString.php index dd3342fd..62c4d576 100644 --- a/src/Types/MultiLineString.php +++ b/src/Types/MultiLineString.php @@ -5,29 +5,22 @@ use GeoJson\GeoJson; use GeoJson\Geometry\MultiLineString as GeoJsonMultiLineString; use Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException; -use InvalidArgumentException; class MultiLineString extends GeometryCollection { /** - * @param LineString[] $lineStrings + * The minimum number of items required to create this collection. + * + * @var int */ - public function __construct(array $lineStrings) - { - if (count($lineStrings) < 1) { - throw new InvalidArgumentException('$lineStrings must contain at least one entry'); - } - - $validated = array_filter($lineStrings, function ($value) { - return $value instanceof LineString; - }); - - if (count($lineStrings) !== count($validated)) { - throw new InvalidArgumentException('$lineStrings must be an array of LineString'); - } + protected $minimumCollectionItems = 1; - parent::__construct($lineStrings); - } + /** + * The class of the items in the collection. + * + * @var string + */ + protected $collectionItemType = LineString::class; public function getLineStrings() { @@ -39,14 +32,14 @@ public function toWKT() return sprintf('MULTILINESTRING(%s)', (string) $this); } - public static function fromString($wktArgument) + public static function fromString($wktArgument, $srid = 0) { $str = preg_split('/\)\s*,\s*\(/', substr(trim($wktArgument), 1, -1)); $lineStrings = array_map(function ($data) { return LineString::fromString($data); }, $str); - return new static($lineStrings); + return new static($lineStrings, $srid); } public function __toString() @@ -58,9 +51,7 @@ public function __toString() public function offsetSet($offset, $value) { - if (!($value instanceof LineString)) { - throw new InvalidArgumentException('$value must be an instance of LineString'); - } + $this->validateItemType($value); parent::offsetSet($offset, $value); } diff --git a/src/Types/MultiPoint.php b/src/Types/MultiPoint.php index fb55f9e8..752967eb 100644 --- a/src/Types/MultiPoint.php +++ b/src/Types/MultiPoint.php @@ -8,19 +8,26 @@ class MultiPoint extends PointCollection { + /** + * The minimum number of items required to create this collection. + * + * @var int + */ + protected $minimumCollectionItems = 1; + public function toWKT() { return sprintf('MULTIPOINT(%s)', (string) $this); } - public static function fromWkt($wkt) + public static function fromWkt($wkt, $srid = 0) { $wktArgument = Geometry::getWKTArgument($wkt); - return static::fromString($wktArgument); + return static::fromString($wktArgument, $srid); } - public static function fromString($wktArgument) + public static function fromString($wktArgument, $srid = 0) { $matches = []; preg_match_all('/\(\s*(\d+\s+\d+)\s*\)/', trim($wktArgument), $matches); @@ -29,7 +36,7 @@ public static function fromString($wktArgument) return Point::fromPair($pair); }, $matches[1]); - return new static($points); + return new static($points, $srid); } public function __toString() diff --git a/src/Types/MultiPolygon.php b/src/Types/MultiPolygon.php index aba2b6d1..cdea3a9c 100644 --- a/src/Types/MultiPolygon.php +++ b/src/Types/MultiPolygon.php @@ -5,24 +5,22 @@ use GeoJson\GeoJson; use GeoJson\Geometry\MultiPolygon as GeoJsonMultiPolygon; use Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException; -use InvalidArgumentException; class MultiPolygon extends GeometryCollection { /** - * @param Polygon[] $polygons + * The minimum number of items required to create this collection. + * + * @var int */ - public function __construct(array $polygons) - { - $validated = array_filter($polygons, function ($value) { - return $value instanceof Polygon; - }); + protected $minimumCollectionItems = 1; - if (count($polygons) !== count($validated)) { - throw new InvalidArgumentException('$polygons must be an array of Polygon'); - } - parent::__construct($polygons); - } + /** + * The class of the items in the collection. + * + * @var string + */ + protected $collectionItemType = Polygon::class; public function toWKT() { @@ -36,14 +34,14 @@ public function __toString() }, $this->items)); } - public static function fromString($wktArgument) + public static function fromString($wktArgument, $srid = 0) { $parts = preg_split('/(\)\s*\)\s*,\s*\(\s*\()/', $wktArgument, -1, PREG_SPLIT_DELIM_CAPTURE); $polygons = static::assembleParts($parts); return new static(array_map(function ($polygonString) { return Polygon::fromString($polygonString); - }, $polygons)); + }, $polygons), $srid); } /** @@ -93,9 +91,7 @@ protected static function assembleParts(array $parts) public function offsetSet($offset, $value) { - if (!($value instanceof Polygon)) { - throw new InvalidArgumentException('$value must be an instance of Polygon'); - } + $this->validateItemType($value); parent::offsetSet($offset, $value); } diff --git a/src/Types/Point.php b/src/Types/Point.php index 40719af4..d424ec5e 100644 --- a/src/Types/Point.php +++ b/src/Types/Point.php @@ -12,8 +12,10 @@ class Point extends Geometry protected $lng; - public function __construct($lat, $lng) + public function __construct($lat, $lng, $srid = 0) { + parent::__construct($srid); + $this->lat = (float) $lat; $this->lng = (float) $lng; } @@ -43,11 +45,11 @@ public function toPair() return $this->getLng().' '.$this->getLat(); } - public static function fromPair($pair) + public static function fromPair($pair, $srid = 0) { list($lng, $lat) = explode(' ', trim($pair, "\t\n\r \x0B()")); - return new static((float) $lat, (float) $lng); + return new static((float) $lat, (float) $lng, (int) $srid); } public function toWKT() @@ -55,9 +57,9 @@ public function toWKT() return sprintf('POINT(%s)', (string) $this); } - public static function fromString($wktArgument) + public static function fromString($wktArgument, $srid = 0) { - return static::fromPair($wktArgument); + return static::fromPair($wktArgument, $srid); } public function __toString() diff --git a/src/Types/PointCollection.php b/src/Types/PointCollection.php index af586b28..30d1b8de 100755 --- a/src/Types/PointCollection.php +++ b/src/Types/PointCollection.php @@ -8,24 +8,11 @@ abstract class PointCollection extends GeometryCollection { /** - * @param Point[] $points + * The class of the items in the collection. + * + * @var string */ - public function __construct(array $points) - { - if (count($points) < 2) { - throw new InvalidArgumentException('$points must contain at least two entries'); - } - - $validated = array_filter($points, function ($value) { - return $value instanceof Point; - }); - - if (count($points) !== count($validated)) { - throw new InvalidArgumentException('$points must be an array of Points'); - } - - parent::__construct($points); - } + protected $collectionItemType = Point::class; public function toPairList() { @@ -36,9 +23,7 @@ public function toPairList() public function offsetSet($offset, $value) { - if (!($value instanceof Point)) { - throw new InvalidArgumentException('$value must be an instance of Point'); - } + $this->validateItemType($value); parent::offsetSet($offset, $value); } diff --git a/tests/Integration/IntegrationBaseTestCase.php b/tests/Integration/IntegrationBaseTestCase.php new file mode 100644 index 00000000..04634734 --- /dev/null +++ b/tests/Integration/IntegrationBaseTestCase.php @@ -0,0 +1,110 @@ +register(SpatialServiceProvider::class); + + $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + + $app['config']->set('database.default', 'mysql'); + $app['config']->set('database.connections.mysql.host', env('DB_HOST')); + $app['config']->set('database.connections.mysql.port', env('DB_PORT')); + $app['config']->set('database.connections.mysql.database', env('DB_DATABASE')); + $app['config']->set('database.connections.mysql.username', env('DB_USERNAME')); + $app['config']->set('database.connections.mysql.password', env('DB_PASSWORD')); + $app['config']->set('database.connections.mysql.modes', [ + 'ONLY_FULL_GROUP_BY', + 'STRICT_TRANS_TABLES', + 'NO_ZERO_IN_DATE', + 'NO_ZERO_DATE', + 'ERROR_FOR_DIVISION_BY_ZERO', + 'NO_ENGINE_SUBSTITUTION', + ]); + + return $app; + } + + /** + * Setup DB before each test. + * + * @return void + */ + public function setUp() + { + parent::setUp(); + + $this->after_fix = $this->isMySQL8AfterFix(); + + $this->onMigrations(function ($migrationClass) { + (new $migrationClass())->up(); + }); + + //\DB::listen(function($sql) { + // var_dump($sql); + //}); + } + + public function tearDown() + { + $this->onMigrations(function ($migrationClass) { + (new $migrationClass())->down(); + }, true); + + parent::tearDown(); + } + + // MySQL 8.0.4 fixed bug #26941370 and bug #88031 + private function isMySQL8AfterFix() + { + $results = DB::select(DB::raw('select version()')); + $mysql_version = $results[0]->{'version()'}; + + return version_compare($mysql_version, '8.0.4', '>='); + } + + protected function assertDatabaseHas($table, array $data, $connection = null) + { + if (method_exists($this, 'seeInDatabase')) { + $this->seeInDatabase($table, $data, $connection); + } else { + parent::assertDatabaseHas($table, $data, $connection); + } + } + + protected function assertException($exceptionName, $exceptionMessage = null) + { + if (method_exists(parent::class, 'expectException')) { + parent::expectException($exceptionName); + if (!is_null($exceptionMessage)) { + $this->expectExceptionMessage($exceptionMessage); + } + } else { + $this->setExpectedException($exceptionName, $exceptionMessage); + } + } + + private function onMigrations(\Closure $closure, $reverse_sort = false) + { + $migrations = $this->migrations; + $reverse_sort ? rsort($migrations, SORT_STRING) : sort($migrations, SORT_STRING); + + foreach ($migrations as $migrationClass) { + $closure($migrationClass); + } + } +} diff --git a/tests/Integration/MigrationTest.php b/tests/Integration/MigrationTest.php new file mode 100644 index 00000000..6b740d0f --- /dev/null +++ b/tests/Integration/MigrationTest.php @@ -0,0 +1,56 @@ +assertEquals('geometry', $result->Table); + $this->assertEquals($expected, $result->{'Create Table'}); + } + + public function testTableWasCreatedWithSrid() + { + $result = DB::selectOne('SHOW CREATE TABLE with_srid'); + + $expected = 'CREATE TABLE `with_srid` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `geo` geometry /*!80003 SRID 3857 */ DEFAULT NULL, + `location` point /*!80003 SRID 3857 */ DEFAULT NULL, + `line` linestring /*!80003 SRID 3857 */ DEFAULT NULL, + `shape` polygon /*!80003 SRID 3857 */ DEFAULT NULL, + `multi_locations` multipoint /*!80003 SRID 3857 */ DEFAULT NULL, + `multi_lines` multilinestring /*!80003 SRID 3857 */ DEFAULT NULL, + `multi_shapes` multipolygon /*!80003 SRID 3857 */ DEFAULT NULL, + `multi_geometries` geomcollection /*!80003 SRID 3857 */ DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'; + + $this->assertEquals('with_srid', $result->Table); + $this->assertEquals($expected, $result->{'Create Table'}); + } +} diff --git a/tests/Integration/Migrations/CreateTables.php b/tests/Integration/Migrations/CreateTables.php index 93f08704..fdff4f58 100644 --- a/tests/Integration/Migrations/CreateTables.php +++ b/tests/Integration/Migrations/CreateTables.php @@ -1,7 +1,7 @@ charset = 'utf8mb4'; + $table->collation = 'utf8mb4_unicode_ci'; $table->increments('id'); $table->geometry('geo')->default(null)->nullable(); $table->point('location'); // required to be not null in order to add an index @@ -30,6 +32,20 @@ public function up() $table->increments('id'); $table->geometry('geometry')->default(null)->nullable(); }); + + Schema::create('with_srid', function (Blueprint $table) { + $table->charset = 'utf8mb4'; + $table->collation = 'utf8mb4_unicode_ci'; + $table->increments('id'); + $table->geometry('geo', 3857)->default(null)->nullable(); + $table->point('location', 3857)->default(null)->nullable(); + $table->lineString('line', 3857)->default(null)->nullable(); + $table->polygon('shape', 3857)->default(null)->nullable(); + $table->multiPoint('multi_locations', 3857)->default(null)->nullable(); + $table->multiLineString('multi_lines', 3857)->default(null)->nullable(); + $table->multiPolygon('multi_shapes', 3857)->default(null)->nullable(); + $table->geometryCollection('multi_geometries', 3857)->default(null)->nullable(); + }); } /** @@ -41,5 +57,6 @@ public function down() { Schema::drop('geometry'); Schema::drop('no_spatial_fields'); + Schema::drop('with_srid'); } } diff --git a/tests/Integration/Migrations/UpdateTables.php b/tests/Integration/Migrations/UpdateTables.php index 46ebaccc..83915b81 100644 --- a/tests/Integration/Migrations/UpdateTables.php +++ b/tests/Integration/Migrations/UpdateTables.php @@ -1,7 +1,7 @@ exec(sprintf('DROP DATABASE IF EXISTS %s', $database)); - if ($recreate) { - $pdo->exec(sprintf( - 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s;', - $database, - env('DB_CHARSET', 'utf8mb4'), - env('DB_COLLATION', 'utf8mb4_unicode_ci') - )); - } - } catch (RuntimeException $exception) { - throw $exception; - } - } - - /** - * Boots the application. - * - * @return \Illuminate\Foundation\Application - */ - public function createApplication() - { - $app = require __DIR__.'/../../vendor/laravel/laravel/bootstrap/app.php'; - $app->register(SpatialServiceProvider::class); - - $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap(); - - $app['config']->set('database.default', 'mysql'); - $app['config']->set('database.connections.mysql.host', env('DB_HOST')); - $app['config']->set('database.connections.mysql.port', env('DB_PORT')); - $app['config']->set('database.connections.mysql.database', env('DB_DATABASE')); - $app['config']->set('database.connections.mysql.username', env('DB_USERNAME')); - $app['config']->set('database.connections.mysql.password', env('DB_PASSWORD')); - $app['config']->set('database.connections.mysql.modes', [ - 'ONLY_FULL_GROUP_BY', - 'STRICT_TRANS_TABLES', - 'NO_ZERO_IN_DATE', - 'NO_ZERO_DATE', - 'ERROR_FOR_DIVISION_BY_ZERO', - 'NO_ENGINE_SUBSTITUTION', - ]); - - return $app; - } - - /** - * Setup DB before each test. - * - * @return void - */ - public function setUp() - { - parent::setUp(); - - $this->after_fix = $this->isMySQL8AfterFix(); - - $this->onMigrations(function ($migrationClass) { - (new $migrationClass())->up(); - }); - -// \DB::listen(function($sql) { -// var_dump($sql); -// }); - } - - public function tearDown() - { - $this->onMigrations(function ($migrationClass) { - (new $migrationClass())->down(); - }, true); - - parent::tearDown(); - } - - // MySQL 8.0.4 fixed bug #26941370 and bug #88031 - private function isMySQL8AfterFix() - { - $results = DB::select(DB::raw('select version()')); - $mysql_version = $results[0]->{'version()'}; - - return version_compare($mysql_version, '8.0.4', '>='); - } - - protected function assertDatabaseHas($table, array $data, $connection = null) - { - if (method_exists($this, 'seeInDatabase')) { - $this->seeInDatabase($table, $data, $connection); - } else { - parent::assertDatabaseHas($table, $data, $connection); - } - } - - protected function assertException($exceptionName) - { - if (method_exists(parent::class, 'expectException')) { - parent::expectException($exceptionName); - } else { - $this->setExpectedException($exceptionName); - } - } - - private function onMigrations(\Closure $closure, $reverse_sort = false) - { - $fileSystem = new Filesystem(); - $classFinder = new Tools\ClassFinder(); - - $migrations = $fileSystem->files(__DIR__.'/Migrations'); - $reverse_sort ? rsort($migrations, SORT_STRING) : sort($migrations, SORT_STRING); - - foreach ($migrations as $file) { - $fileSystem->requireOnce($file); - $migrationClass = $classFinder->findClass($file); - - $closure($migrationClass); - } - } + protected $migrations = [ + CreateLocationTable::class, + UpdateLocationTable::class, + ]; public function testSpatialFieldsNotDefinedException() { @@ -242,6 +91,21 @@ public function testInsertGeometryCollection() $this->assertDatabaseHas('geometry', ['id' => $geo->id]); } + public function testInsertEmptyGeometryCollection() + { + $geo = new GeometryModel(); + + $geo->location = new Point(1, 2); + + $geo->multi_geometries = new GeometryCollection([]); + $geo->save(); + $this->assertDatabaseHas('geometry', ['id' => $geo->id]); + + $geo2 = GeometryModel::find($geo->id); + $this->assertInstanceOf(GeometryCollection::class, $geo2->multi_geometries); + $this->assertEquals(0, count($geo2->multi_geometries)); + } + public function testUpdate() { $geo = new GeometryModel(); diff --git a/tests/Integration/SridSpatialTest.php b/tests/Integration/SridSpatialTest.php new file mode 100644 index 00000000..f57c6cb2 --- /dev/null +++ b/tests/Integration/SridSpatialTest.php @@ -0,0 +1,136 @@ +location = new Point(1, 2, 3857); + $geo->save(); + $this->assertDatabaseHas('with_srid', ['id' => $geo->id]); + } + + public function testInsertLineStringWithSrid() + { + $geo = new WithSridModel(); + + $geo->location = new Point(1, 2, 3857); + $geo->line = new LineString([new Point(1, 1), new Point(2, 2)], 3857); + $geo->save(); + $this->assertDatabaseHas('with_srid', ['id' => $geo->id]); + } + + public function testInsertPolygonWithSrid() + { + $geo = new WithSridModel(); + + $geo->location = new Point(1, 2, 3857); + $geo->shape = Polygon::fromWKT('POLYGON((0 10,10 10,10 0,0 0,0 10))', 3857); + $geo->save(); + $this->assertDatabaseHas('with_srid', ['id' => $geo->id]); + } + + public function testInsertMultiPointWithSrid() + { + $geo = new WithSridModel(); + + $geo->location = new Point(1, 2, 3857); + $geo->multi_locations = new MultiPoint([new Point(1, 1), new Point(2, 2)], 3857); + $geo->save(); + $this->assertDatabaseHas('with_srid', ['id' => $geo->id]); + } + + public function testInsertMultiPolygonWithSrid() + { + $geo = new WithSridModel(); + + $geo->location = new Point(1, 2, 3857); + + $geo->multi_shapes = new MultiPolygon([ + Polygon::fromWKT('POLYGON((0 10,10 10,10 0,0 0,0 10))'), + Polygon::fromWKT('POLYGON((0 0,0 5,5 5,5 0,0 0))'), + ], 3857); + $geo->save(); + $this->assertDatabaseHas('with_srid', ['id' => $geo->id]); + } + + public function testInsertGeometryCollectionWithSrid() + { + $geo = new WithSridModel(); + + $geo->location = new Point(1, 2, 3857); + + $geo->multi_geometries = new GeometryCollection([ + Polygon::fromWKT('POLYGON((0 10,10 10,10 0,0 0,0 10))'), + Polygon::fromWKT('POLYGON((0 0,0 5,5 5,5 0,0 0))'), + new Point(0, 0), + ], 3857); + $geo->save(); + $this->assertDatabaseHas('with_srid', ['id' => $geo->id]); + } + + public function testUpdateWithSrid() + { + $geo = new WithSridModel(); + $geo->location = new Point(1, 2, 3857); + $geo->save(); + + $to_update = WithSridModel::all()->first(); + $to_update->location = new Point(2, 3, 3857); + $to_update->save(); + + $this->assertDatabaseHas('with_srid', ['id' => $to_update->id]); + + $all = WithSridModel::all(); + $this->assertCount(1, $all); + + $updated = $all->first(); + $this->assertInstanceOf(Point::class, $updated->location); + $this->assertEquals(2, $updated->location->getLat()); + $this->assertEquals(3, $updated->location->getLng()); + } + + public function testInsertPointWithWrongSrid() + { + $geo = new WithSridModel(); + $geo->location = new Point(1, 2); + + $this->assertException( + Illuminate\Database\QueryException::class, + 'SQLSTATE[HY000]: General error: 3643 The SRID of the geometry '. + 'does not match the SRID of the column \'location\'. The SRID '. + 'of the geometry is 0, but the SRID of the column is 3857. '. + 'Consider changing the SRID of the geometry or the SRID property '. + 'of the column. (SQL: insert into `with_srid` (`location`) values '. + '(ST_GeomFromText(POINT(2 1), 0, \'axis-order=long-lat\')))' + ); + $geo->save(); + } + + public function testGeometryInsertedHasRightSrid() + { + $geo = new WithSridModel(); + $geo->location = new Point(1, 2, 3857); + $geo->save(); + + $srid = \DB::selectOne('select ST_SRID(location) as srid from with_srid'); + $this->assertEquals(3857, $srid->srid); + + $result = WithSridModel::first(); + + $this->assertEquals($geo->location->getSrid(), $result->location->getSrid()); + $a = 1; + } +} diff --git a/tests/Integration/Tools/ClassFinder.php b/tests/Integration/Tools/ClassFinder.php deleted file mode 100644 index 13a8263b..00000000 --- a/tests/Integration/Tools/ClassFinder.php +++ /dev/null @@ -1,154 +0,0 @@ -in($directory)->name('*.php') as $file) { - $classes[] = $this->findClass($file->getRealPath()); - } - - return array_filter($classes); - } - - /** - * Extract the class name from the file at the given path. - * - * @param string $path - * - * @return string|null - */ - public function findClass($path) - { - $namespace = null; - - $tokens = token_get_all(file_get_contents($path)); - - foreach ($tokens as $key => $token) { - if ($this->tokenIsNamespace($token)) { - $namespace = $this->getNamespace($key + 2, $tokens); - } elseif ($this->tokenIsClassOrInterface($token)) { - return ltrim($namespace.'\\'.$this->getClass($key + 2, $tokens), '\\'); - } - } - } - - /** - * Find the namespace in the tokens starting at a given key. - * - * @param int $key - * @param array $tokens - * - * @return string - */ - protected function getNamespace($key, array $tokens) - { - $namespace = null; - - $tokenCount = count($tokens); - - for ($i = $key; $i < $tokenCount; $i++) { - if ($this->isPartOfNamespace($tokens[$i])) { - $namespace .= $tokens[$i][1]; - } elseif ($tokens[$i] == ';') { - return $namespace; - } - } - } - - /** - * Find the class in the tokens starting at a given key. - * - * @param int $key - * @param array $tokens - * - * @return string - */ - protected function getClass($key, array $tokens) - { - $class = null; - - $tokenCount = count($tokens); - - for ($i = $key; $i < $tokenCount; $i++) { - if ($this->isPartOfClass($tokens[$i])) { - $class .= $tokens[$i][1]; - } elseif ($this->isWhitespace($tokens[$i])) { - return $class; - } - } - } - - /** - * Determine if the given token is a namespace keyword. - * - * @param array|string $token - * - * @return bool - */ - protected function tokenIsNamespace($token) - { - return is_array($token) && $token[0] == T_NAMESPACE; - } - - /** - * Determine if the given token is a class or interface keyword. - * - * @param array|string $token - * - * @return bool - */ - protected function tokenIsClassOrInterface($token) - { - return is_array($token) && ($token[0] == T_CLASS || $token[0] == T_INTERFACE); - } - - /** - * Determine if the given token is part of the namespace. - * - * @param array|string $token - * - * @return bool - */ - protected function isPartOfNamespace($token) - { - return is_array($token) && ($token[0] == T_STRING || $token[0] == T_NS_SEPARATOR); - } - - /** - * Determine if the given token is part of the class. - * - * @param array|string $token - * - * @return bool - */ - protected function isPartOfClass($token) - { - return is_array($token) && $token[0] == T_STRING; - } - - /** - * Determine if the given token is whitespace. - * - * @param array|string $token - * - * @return bool - */ - protected function isWhitespace($token) - { - return is_array($token) && $token[0] == T_WHITESPACE; - } -} diff --git a/tests/Unit/BaseTestCase.php b/tests/Unit/BaseTestCase.php index 44f323c5..219f737d 100644 --- a/tests/Unit/BaseTestCase.php +++ b/tests/Unit/BaseTestCase.php @@ -1,18 +1,22 @@ setExpectedException($exceptionName); + $this->setExpectedException($exceptionName, $exceptionMessage, $exceptionCode); } } } diff --git a/tests/Unit/Eloquent/BuilderTest.php b/tests/Unit/Eloquent/BuilderTest.php index b77c6c4e..d9a12a55 100644 --- a/tests/Unit/Eloquent/BuilderTest.php +++ b/tests/Unit/Eloquent/BuilderTest.php @@ -41,9 +41,12 @@ public function testUpdatePoint() $this->queryBuilder ->shouldReceive('update') ->with(['point' => new SpatialExpression($point)]) - ->once(); + ->once() + ->andReturn(1); + + $result = $this->builder->update(['point' => $point]); - $this->builder->update(['point' => $point]); + $this->assertSame(1, $result); } public function testUpdateLinestring() @@ -53,9 +56,12 @@ public function testUpdateLinestring() $this->queryBuilder ->shouldReceive('update') ->with(['linestring' => new SpatialExpression($linestring)]) - ->once(); + ->once() + ->andReturn(1); - $this->builder->update(['linestring' => $linestring]); + $result = $this->builder->update(['linestring' => $linestring]); + + $this->assertSame(1, $result); } public function testUpdatePolygon() @@ -68,9 +74,59 @@ public function testUpdatePolygon() $this->queryBuilder ->shouldReceive('update') ->with(['polygon' => new SpatialExpression($polygon)]) - ->once(); + ->once() + ->andReturn(1); + + $result = $this->builder->update(['polygon' => $polygon]); + + $this->assertSame(1, $result); + } + + public function testUpdatePointWithSrid() + { + $point = new Point(1, 2, 4326); + $this->queryBuilder + ->shouldReceive('update') + ->with(['point' => new SpatialExpression($point)]) + ->once() + ->andReturn(1); + + $result = $this->builder->update(['point' => $point]); + + $this->assertSame(1, $result); + } + + public function testUpdateLinestringWithSrid() + { + $linestring = new LineString([new Point(0, 0), new Point(1, 1), new Point(2, 2)], 4326); + + $this->queryBuilder + ->shouldReceive('update') + ->with(['linestring' => new SpatialExpression($linestring)]) + ->once() + ->andReturn(1); + + $result = $this->builder->update(['linestring' => $linestring]); + + $this->assertSame(1, $result); + } + + public function testUpdatePolygonWithSrid() + { + $linestrings[] = new LineString([new Point(0, 0), new Point(0, 1)]); + $linestrings[] = new LineString([new Point(0, 1), new Point(1, 1)]); + $linestrings[] = new LineString([new Point(1, 1), new Point(0, 0)]); + $polygon = new Polygon($linestrings, 4326); + + $this->queryBuilder + ->shouldReceive('update') + ->with(['polygon' => new SpatialExpression($polygon)]) + ->once() + ->andReturn(1); + + $result = $this->builder->update(['polygon' => $polygon]); - $this->builder->update(['polygon' => $polygon]); + $this->assertSame(1, $result); } } diff --git a/tests/Unit/Eloquent/SpatialTraitTest.php b/tests/Unit/Eloquent/SpatialTraitTest.php index 717e8897..8ece7d25 100644 --- a/tests/Unit/Eloquent/SpatialTraitTest.php +++ b/tests/Unit/Eloquent/SpatialTraitTest.php @@ -4,10 +4,13 @@ use Grimzy\LaravelMysqlSpatial\MysqlConnection; use Grimzy\LaravelMysqlSpatial\Types\Point; use Illuminate\Database\Eloquent\Model; +use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery as m; class SpatialTraitTest extends BaseTestCase { + use MockeryPHPUnitIntegration; + /** * @var TestModel */ @@ -37,7 +40,7 @@ public function testInsertUpdatePointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`point`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`point`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); @@ -45,7 +48,7 @@ public function testInsertUpdatePointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `point` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `point` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -60,7 +63,7 @@ public function testInsertUpdateLineStringHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`linestring`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`linestring`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); @@ -68,7 +71,7 @@ public function testInsertUpdateLineStringHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `linestring` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `linestring` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -87,14 +90,14 @@ public function testInsertUpdatePolygonHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`polygon`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`polygon`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->polygon = new \Grimzy\LaravelMysqlSpatial\Types\Polygon([$linestring1, $linestring2]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `polygon` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `polygon` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -109,7 +112,7 @@ public function testInsertUpdateMultiPointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`multipoint`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`multipoint`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); @@ -117,7 +120,7 @@ public function testInsertUpdateMultiPointHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `multipoint` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `multipoint` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -136,14 +139,14 @@ public function testInsertUpdateMultiLineStringHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`multilinestring`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`multilinestring`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->multilinestring = new \Grimzy\LaravelMysqlSpatial\Types\MultiLineString([$linestring1, $linestring2]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `multilinestring` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `multilinestring` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -171,14 +174,14 @@ public function testInsertUpdateMultiPolygonHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`multipolygon`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`multipolygon`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->multipolygon = new \Grimzy\LaravelMysqlSpatial\Types\MultiPolygon([$polygon1, $polygon2]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `multipolygon` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `multipolygon` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -195,14 +198,14 @@ public function testInsertUpdateGeometryCollectionHasCorrectSql() $this->model->save(); $this->assertStringStartsWith('insert', $this->queries[0]); - $this->assertContains('insert into `test_models` (`geometrycollection`) values (ST_GeomFromText(?))', $this->queries[0]); + $this->assertContains('insert into `test_models` (`geometrycollection`) values (ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $this->queries[0]); // TODO: assert bindings in query $this->assertTrue($this->model->exists); $this->model->geometrycollection = new \Grimzy\LaravelMysqlSpatial\Types\GeometryCollection([$point1, $linestring1]); $this->model->save(); $this->assertStringStartsWith('update', $this->queries[1]); - $this->assertContains('update `test_models` set `geometrycollection` = ST_GeomFromText(?) where `id` = ?', $this->queries[1]); + $this->assertContains('update `test_models` set `geometrycollection` = ST_GeomFromText(?, ?, \'axis-order=long-lat\') where `id` = ?', $this->queries[1]); // TODO: assert bindings in query } @@ -217,7 +220,10 @@ public function testSettingRawAttributes() public function testSpatialFieldsNotDefinedException() { $model = new TestNoSpatialModel(); - $this->setExpectedException(SpatialFieldsNotDefinedException::class); + $this->assertException( + SpatialFieldsNotDefinedException::class, + 'TestNoSpatialModel has to define $spatialFields' + ); $model->getSpatialFields(); } @@ -231,9 +237,9 @@ public function testScopeDistance() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); + $this->assertEquals(10, $bindings[2]); } public function testScopeDistanceExcludingSelf() @@ -246,11 +252,11 @@ public function testScopeDistanceExcludingSelf() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) != 0', $q->wheres[1]['sql']); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) != 0', $q->wheres[1]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); - $this->assertEquals('POINT(2 1)', $bindings[2]); + $this->assertEquals(10, $bindings[2]); + $this->assertEquals('POINT(2 1)', $bindings[3]); } public function testScopeDistanceSphere() @@ -263,9 +269,9 @@ public function testScopeDistanceSphere() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); + $this->assertEquals(10, $bindings[2]); } public function testScopeDistanceSphereExcludingSelf() @@ -278,11 +284,11 @@ public function testScopeDistanceSphereExcludingSelf() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); - $this->assertEquals('st_distance_sphere(point, ST_GeomFromText(?)) != 0', $q->wheres[1]['sql']); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('st_distance_sphere(point, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) != 0', $q->wheres[1]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); - $this->assertEquals(10, $bindings[1]); - $this->assertEquals('POINT(2 1)', $bindings[2]); + $this->assertEquals(10, $bindings[2]); + $this->assertEquals('POINT(2 1)', $bindings[3]); } public function testScopeDistanceValue() @@ -297,7 +303,7 @@ public function testScopeDistanceValue() $this->assertNotEmpty($bindings); $this->assertEquals('*', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -313,7 +319,7 @@ public function testScopeDistanceValueWithSelect() $this->assertNotEmpty($bindings); $this->assertEquals('some_column', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -329,7 +335,7 @@ public function testScopeDistanceSphereValue() $this->assertNotEmpty($bindings); $this->assertEquals('*', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -345,7 +351,7 @@ public function testScopeDistanceSphereValueWithSelect() $this->assertNotEmpty($bindings); $this->assertEquals('some_column', $q->columns[0]); $this->assertInstanceOf(\Illuminate\Database\Query\Expression::class, $q->columns[1]); - $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?)) as distance', $q->columns[1]->getValue()); + $this->assertEquals('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) as distance', $q->columns[1]->getValue()); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -373,7 +379,7 @@ public function testScopeComparison() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_within(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_within(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -386,7 +392,7 @@ public function testScopeWithin() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_within(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_within(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -399,7 +405,7 @@ public function testScopeCrosses() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_crosses(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_crosses(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -412,7 +418,7 @@ public function testScopeContains() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_contains(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_contains(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -425,7 +431,7 @@ public function testScopeDisjoint() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_disjoint(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_disjoint(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -438,7 +444,7 @@ public function testScopeEquals() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_equals(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_equals(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -451,7 +457,7 @@ public function testScopeIntersects() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_intersects(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_intersects(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -464,7 +470,7 @@ public function testScopeOverlaps() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_overlaps(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_overlaps(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } @@ -477,14 +483,17 @@ public function testScopeDoesTouch() $this->assertNotEmpty($q->wheres); $bindings = $q->getRawBindings()['where']; $this->assertNotEmpty($bindings); - $this->assertContains('st_touches(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertContains('st_touches(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\'))', $q->wheres[0]['sql']); $this->assertEquals('POLYGON((1 1,2 1),(2 1,2 2),(2 2,1 1))', $bindings[0]); } public function testScopeOrderBySpatialThrowsExceptionWhenFunctionNotRegistered() { $point = new Point(1, 2); - $this->assertException(\Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialFunctionException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\UnknownSpatialFunctionException::class, + 'does-not-exist' + ); TestModel::orderBySpatial('point', $point, 'does-not-exist'); } @@ -498,7 +507,7 @@ public function testScopeOrderByDistance() $this->assertNotEmpty($q->orders); $bindings = $q->getRawBindings()['order']; $this->assertNotEmpty($bindings); - $this->assertContains('st_distance(`point`, ST_GeomFromText(?)) asc', $q->orders[0]['sql']); + $this->assertContains('st_distance(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) asc', $q->orders[0]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); } @@ -512,7 +521,7 @@ public function testScopeOrderByDistanceSphere() $this->assertNotEmpty($q->orders); $bindings = $q->getRawBindings()['order']; $this->assertNotEmpty($bindings); - $this->assertContains('st_distance_sphere(`point`, ST_GeomFromText(?)) asc', $q->orders[0]['sql']); + $this->assertContains('st_distance_sphere(`point`, ST_GeomFromText(?, ?, \'axis-order=long-lat\')) asc', $q->orders[0]['sql']); $this->assertEquals('POINT(2 1)', $bindings[0]); } } diff --git a/tests/Unit/MysqlConnectionTest.php b/tests/Unit/MysqlConnectionTest.php index c630d990..cae970a2 100644 --- a/tests/Unit/MysqlConnectionTest.php +++ b/tests/Unit/MysqlConnectionTest.php @@ -2,9 +2,10 @@ use Grimzy\LaravelMysqlSpatial\MysqlConnection; use Grimzy\LaravelMysqlSpatial\Schema\Builder; +use PHPUnit\Framework\TestCase; use Stubs\PDOStub; -class MysqlConnectionTest extends PHPUnit_Framework_TestCase +class MysqlConnectionTest extends TestCase { private $mysqlConnection; diff --git a/tests/Unit/Schema/BlueprintTest.php b/tests/Unit/Schema/BlueprintTest.php index da335520..c22c9518 100644 --- a/tests/Unit/Schema/BlueprintTest.php +++ b/tests/Unit/Schema/BlueprintTest.php @@ -4,10 +4,14 @@ use BaseTestCase; use Grimzy\LaravelMysqlSpatial\Schema\Blueprint; +use Illuminate\Database\Schema\ColumnDefinition; use Mockery; class BlueprintTest extends BaseTestCase { + /** + * @var \Grimzy\LaravelMysqlSpatial\Schema\Blueprint + */ protected $blueprint; public function setUp() @@ -20,81 +24,305 @@ public function setUp() public function testGeometry() { + $expectedCol = new ColumnDefinition([ + 'type' => 'geometry', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('geometry', 'col') - ->once(); + ->with('geometry', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->geometry('col'); - $this->blueprint->geometry('col'); + $this->assertSame($expectedCol, $result); } public function testPoint() { + $expectedCol = new ColumnDefinition([ + 'type' => 'point', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') ->with('point', 'col', ['srid' => null]) - ->once(); + ->once() + ->andReturn($expectedCol); - $this->blueprint->point('col'); + $result = $this->blueprint->point('col'); + + $this->assertSame($expectedCol, $result); } public function testLinestring() { + $expectedCol = new ColumnDefinition([ + 'type' => 'linestring', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('linestring', 'col') - ->once(); + ->with('linestring', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); - $this->blueprint->linestring('col'); + $result = $this->blueprint->linestring('col'); + + $this->assertSame($expectedCol, $result); } public function testPolygon() { + $expectedCol = new ColumnDefinition([ + 'type' => 'polygon', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('polygon', 'col') - ->once(); + ->with('polygon', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->polygon('col'); - $this->blueprint->polygon('col'); + $this->assertSame($expectedCol, $result); } public function testMultiPoint() { + $expectedCol = new ColumnDefinition([ + 'type' => 'multipoint', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('multipoint', 'col') - ->once(); + ->with('multipoint', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->multipoint('col'); - $this->blueprint->multipoint('col'); + $this->assertSame($expectedCol, $result); } public function testMultiLineString() { + $expectedCol = new ColumnDefinition([ + 'type' => 'multilinestring', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('multilinestring', 'col') - ->once(); + ->with('multilinestring', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->multilinestring('col'); - $this->blueprint->multilinestring('col'); + $this->assertSame($expectedCol, $result); } - public function testMulltiPolygon() + public function testMultiPolygon() { + $expectedCol = new ColumnDefinition([ + 'type' => 'multipolygon', + 'name' => 'col', + 'srid' => null, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('multipolygon', 'col') - ->once(); + ->with('multipolygon', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); - $this->blueprint->multipolygon('col'); + $result = $this->blueprint->multipolygon('col'); + + $this->assertSame($expectedCol, $result); } public function testGeometryCollection() { + $expectedCol = new ColumnDefinition([ + 'type' => 'geometrycollection', + 'name' => 'col', + 'srid' => null, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('geometrycollection', 'col', ['srid' => null]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->geometrycollection('col'); + + $this->assertSame($expectedCol, $result); + } + + public function testGeometryWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'geometry', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('geometry', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->geometry('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testPointWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'point', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('point', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->point('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testLinestringWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'linestring', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('linestring', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->linestring('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testPolygonWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'polygon', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('polygon', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->polygon('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testMultiPointWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'multipoint', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('multipoint', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->multipoint('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testMultiLineStringWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'multilinestring', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('multilinestring', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->multilinestring('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testMultiPolygonWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'multipolygon', + 'name' => 'col', + 'srid' => 4326, + ]); + + $this->blueprint + ->shouldReceive('addColumn') + ->with('multipolygon', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->multipolygon('col', 4326); + + $this->assertSame($expectedCol, $result); + } + + public function testGeometryCollectionWithSrid() + { + $expectedCol = new ColumnDefinition([ + 'type' => 'geometrycollection', + 'name' => 'col', + 'srid' => 4326, + ]); + $this->blueprint ->shouldReceive('addColumn') - ->with('geometrycollection', 'col') - ->once(); + ->with('geometrycollection', 'col', ['srid' => 4326]) + ->once() + ->andReturn($expectedCol); + + $result = $this->blueprint->geometrycollection('col', 4326); - $this->blueprint->geometrycollection('col'); + $this->assertSame($expectedCol, $result); } } diff --git a/tests/Unit/Schema/Grammars/MySqlGrammarTest.php b/tests/Unit/Schema/Grammars/MySqlGrammarTest.php index 4773a2ea..66ba7641 100644 --- a/tests/Unit/Schema/Grammars/MySqlGrammarTest.php +++ b/tests/Unit/Schema/Grammars/MySqlGrammarTest.php @@ -13,7 +13,7 @@ public function testAddingGeometry() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('GEOMETRY', $statements[0]); + $this->assertEquals('alter table `test` add `foo` GEOMETRY not null', $statements[0]); } public function testAddingPoint() @@ -23,7 +23,7 @@ public function testAddingPoint() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('POINT', $statements[0]); + $this->assertEquals('alter table `test` add `foo` POINT not null', $statements[0]); } public function testAddingLinestring() @@ -33,7 +33,7 @@ public function testAddingLinestring() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('LINESTRING', $statements[0]); + $this->assertEquals('alter table `test` add `foo` LINESTRING not null', $statements[0]); } public function testAddingPolygon() @@ -43,7 +43,7 @@ public function testAddingPolygon() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('POLYGON', $statements[0]); + $this->assertEquals('alter table `test` add `foo` POLYGON not null', $statements[0]); } public function testAddingMultipoint() @@ -53,7 +53,7 @@ public function testAddingMultipoint() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('MULTIPOINT', $statements[0]); + $this->assertEquals('alter table `test` add `foo` MULTIPOINT not null', $statements[0]); } public function testAddingMultiLinestring() @@ -63,7 +63,7 @@ public function testAddingMultiLinestring() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('MULTILINESTRING', $statements[0]); + $this->assertEquals('alter table `test` add `foo` MULTILINESTRING not null', $statements[0]); } public function testAddingMultiPolygon() @@ -73,7 +73,7 @@ public function testAddingMultiPolygon() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('MULTIPOLYGON', $statements[0]); + $this->assertEquals('alter table `test` add `foo` MULTIPOLYGON not null', $statements[0]); } public function testAddingGeometryCollection() @@ -83,7 +83,87 @@ public function testAddingGeometryCollection() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(1, count($statements)); - $this->assertContains('GEOMETRYCOLLECTION', $statements[0]); + $this->assertEquals('alter table `test` add `foo` GEOMETRYCOLLECTION not null', $statements[0]); + } + + public function testAddingGeometryWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->geometry('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` GEOMETRY not null srid 4326', $statements[0]); + } + + public function testAddingPointWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->point('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` POINT not null srid 4326', $statements[0]); + } + + public function testAddingLinestringWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->linestring('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` LINESTRING not null srid 4326', $statements[0]); + } + + public function testAddingPolygonWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->polygon('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` POLYGON not null srid 4326', $statements[0]); + } + + public function testAddingMultipointWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->multipoint('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` MULTIPOINT not null srid 4326', $statements[0]); + } + + public function testAddingMultiLinestringWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->multilinestring('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` MULTILINESTRING not null srid 4326', $statements[0]); + } + + public function testAddingMultiPolygonWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->multipolygon('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` MULTIPOLYGON not null srid 4326', $statements[0]); + } + + public function testAddingGeometryCollectionWithSrid() + { + $blueprint = new Blueprint('test'); + $blueprint->geometrycollection('foo', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertEquals('alter table `test` add `foo` GEOMETRYCOLLECTION not null srid 4326', $statements[0]); } public function testAddRemoveSpatialIndex() @@ -94,7 +174,7 @@ public function testAddRemoveSpatialIndex() $addStatements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals(2, count($addStatements)); - $this->assertContains('alter table `test` add spatial `test_foo_spatial`(`foo`)', $addStatements[1]); + $this->assertEquals('alter table `test` add spatial `test_foo_spatial`(`foo`)', $addStatements[1]); $blueprint->dropSpatialIndex(['foo']); $blueprint->dropSpatialIndex('test_foo_spatial'); @@ -102,8 +182,8 @@ public function testAddRemoveSpatialIndex() $expectedSql = 'alter table `test` drop index `test_foo_spatial`'; $this->assertEquals(5, count($dropStatements)); - $this->assertContains($expectedSql, $dropStatements[3]); - $this->assertContains($expectedSql, $dropStatements[4]); + $this->assertEquals($expectedSql, $dropStatements[3]); + $this->assertEquals($expectedSql, $dropStatements[4]); } /** diff --git a/tests/Unit/Types/GeometryCollectionTest.php b/tests/Unit/Types/GeometryCollectionTest.php index 07759c5f..a0d6f016 100644 --- a/tests/Unit/Types/GeometryCollectionTest.php +++ b/tests/Unit/Types/GeometryCollectionTest.php @@ -33,9 +33,34 @@ public function testJsonSerialize() $this->assertSame('{"type":"GeometryCollection","geometries":[{"type":"LineString","coordinates":[[0,0],[1,0],[1,1],[0,1],[0,0]]},{"type":"Point","coordinates":[200,100]}]}', json_encode($this->getGeometryCollection()->jsonSerialize())); } + public function testCanCreateEmptyGeometryCollection() + { + $geometryCollection = new GeometryCollection([]); + $this->assertInstanceOf(GeometryCollection::class, $geometryCollection); + } + + public function testFromWKTWithEmptyGeometryCollection() + { + /** + * @var GeometryCollection + */ + $geometryCollection = GeometryCollection::fromWKT('GEOMETRYCOLLECTION()'); + $this->assertInstanceOf(GeometryCollection::class, $geometryCollection); + + $this->assertEquals(0, $geometryCollection->count()); + } + + public function testToWKTWithEmptyGeometryCollection() + { + $this->assertEquals('GEOMETRYCOLLECTION()', (new GeometryCollection([]))->toWKT()); + } + public function testInvalidArgumentExceptionNotArrayGeometries() { - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\GeometryCollection must be a collection of Grimzy\LaravelMysqlSpatial\Types\GeometryInterface' + ); $geometrycollection = new GeometryCollection([ $this->getPoint(), 1, @@ -85,7 +110,10 @@ public function testArrayAccess() $this->assertEquals($point100, $geometryCollection[100]); // assert invalid - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\GeometryCollection must be a collection of Grimzy\LaravelMysqlSpatial\Types\GeometryInterface' + ); $geometryCollection[] = 1; } @@ -101,7 +129,10 @@ public function testFromJson() public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + sprintf('Expected %s, got %s', GeoJson\Feature\FeatureCollection::class, GeoJson\Geometry\Point::class) + ); GeometryCollection::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); } diff --git a/tests/Unit/Types/GeometryTest.php b/tests/Unit/Types/GeometryTest.php index 8bd765a7..e51022d5 100644 --- a/tests/Unit/Types/GeometryTest.php +++ b/tests/Unit/Types/GeometryTest.php @@ -32,7 +32,10 @@ public function testGetWKTClass() $this->assertEquals(MultiLineString::class, Geometry::getWKTClass('MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))')); $this->assertEquals(MultiPolygon::class, Geometry::getWKTClass('MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))')); $this->assertEquals(GeometryCollection::class, Geometry::getWKTClass('GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4))')); - $this->assertException(UnknownWKTTypeException::class); + $this->assertException( + UnknownWKTTypeException::class, + 'Type was TRIANGLE' + ); Geometry::getWKTClass('TRIANGLE((0 0, 0 9, 9 0, 0 0))'); } diff --git a/tests/Unit/Types/LineStringTest.php b/tests/Unit/Types/LineStringTest.php index 9300e3f1..ce5713d2 100644 --- a/tests/Unit/Types/LineStringTest.php +++ b/tests/Unit/Types/LineStringTest.php @@ -45,7 +45,10 @@ public function testFromJson() public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + sprintf('Expected %s, got %s', \GeoJson\Geometry\LineString::class, GeoJson\Geometry\Point::class) + ); LineString::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); } diff --git a/tests/Unit/Types/MultiLineStringTest.php b/tests/Unit/Types/MultiLineStringTest.php index df7b42e4..16477feb 100644 --- a/tests/Unit/Types/MultiLineStringTest.php +++ b/tests/Unit/Types/MultiLineStringTest.php @@ -45,7 +45,10 @@ public function testFromJson() public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + sprintf('Expected %s, got %s', GeoJson\Geometry\MultiLineString::class, GeoJson\Geometry\Point::class) + ); MultiLineString::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); } @@ -59,13 +62,19 @@ public function testJsonSerialize() public function testInvalidArgumentExceptionAtLeastOneEntry() { - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiLineString must contain at least 1 entry' + ); $multilinestring = new MultiLineString([]); } public function testInvalidArgumentExceptionNotArrayOfLineString() { - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiLineString must be a collection of Grimzy\LaravelMysqlSpatial\Types\LineString' + ); $multilinestring = new MultiLineString([ new LineString([new Point(0, 0), new Point(1, 1)]), new Point(0, 1), @@ -98,7 +107,10 @@ public function testArrayAccess() $this->assertEquals($linestring2, $multilinestring[2]); // assert invalid - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiLineString must be a collection of Grimzy\LaravelMysqlSpatial\Types\LineString' + ); $multilinestring[] = 1; } } diff --git a/tests/Unit/Types/MultiPointTest.php b/tests/Unit/Types/MultiPointTest.php index 1e9bf7f4..a8a94f41 100644 --- a/tests/Unit/Types/MultiPointTest.php +++ b/tests/Unit/Types/MultiPointTest.php @@ -42,7 +42,10 @@ public function testFromJson() public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + sprintf('Expected %s, got %s', GeoJson\Geometry\MultiPoint::class, GeoJson\Geometry\Point::class) + ); MultiPoint::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); } @@ -58,13 +61,19 @@ public function testJsonSerialize() public function testInvalidArgumentExceptionAtLeastOneEntry() { - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiPoint must contain at least 1 entry' + ); $multipoint = new MultiPoint([]); } public function testInvalidArgumentExceptionNotArrayOfLineString() { - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiPoint must be a collection of Grimzy\LaravelMysqlSpatial\Types\Point' + ); $multipoint = new MultiPoint([ new Point(0, 0), 1, @@ -87,7 +96,10 @@ public function testArrayAccess() $this->assertEquals($point2, $multipoint[2]); // assert invalid - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiPoint must be a collection of Grimzy\LaravelMysqlSpatial\Types\Point' + ); $multipoint[] = 1; } @@ -132,7 +144,10 @@ public function testDeprecatedInsertPoint() $this->assertEquals($point2, $multipoint[1]); $this->assertEquals($point3, $multipoint[2]); - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + '$index is greater than the size of the array' + ); $multipoint->insertPoint(100, new Point(100, 100)); } } diff --git a/tests/Unit/Types/MultiPolygonTest.php b/tests/Unit/Types/MultiPolygonTest.php index cf5894ff..3e49d32d 100644 --- a/tests/Unit/Types/MultiPolygonTest.php +++ b/tests/Unit/Types/MultiPolygonTest.php @@ -65,7 +65,10 @@ public function testFromJson() public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + sprintf('Expected %s, got %s', GeoJson\Geometry\MultiPolygon::class, GeoJson\Geometry\Point::class) + ); MultiPolygon::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); } @@ -75,9 +78,21 @@ public function testJsonSerialize() $this->assertSame('{"type":"MultiPolygon","coordinates":[[[[0,0],[1,0],[1,1],[0,1],[0,0]],[[10,10],[20,10],[20,20],[10,20],[10,10]]],[[[100,100],[200,100],[200,200],[100,200],[100,100]]]]}', json_encode($this->getMultiPolygon())); } - public function testInvalidArgumentExceptionNotArrayOfLineString() + public function testInvalidArgumentExceptionAtLeastOneEntry() { - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiPolygon must contain at least 1 entry' + ); + $multipolygon = new MultiPolygon([]); + } + + public function testInvalidArgumentExceptionNotArrayOfPolygon() + { + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiPolygon must be a collection of Grimzy\LaravelMysqlSpatial\Types\Polygon' + ); $multipolygon = new MultiPolygon([ $this->getPolygon1(), $this->getLineString1(), @@ -101,7 +116,10 @@ public function testArrayAccess() $this->assertEquals($polygon2, $multipolygon[2]); // assert invalid - $this->assertException(InvalidArgumentException::class); + $this->assertException( + InvalidArgumentException::class, + 'Grimzy\LaravelMysqlSpatial\Types\MultiPolygon must be a collection of Grimzy\LaravelMysqlSpatial\Types\Polygon' + ); $multipolygon[] = 1; } diff --git a/tests/Unit/Types/PointTest.php b/tests/Unit/Types/PointTest.php index 257f6a45..518a8a56 100644 --- a/tests/Unit/Types/PointTest.php +++ b/tests/Unit/Types/PointTest.php @@ -6,18 +6,19 @@ class PointTest extends BaseTestCase { public function testFromWKT() { - $point = Point::fromWKT('POINT(1 2)'); + $point = Point::fromWKT('POINT(1 2)', 4326); $this->assertInstanceOf(Point::class, $point); - $this->assertEquals(2, $point->getLat()); - $this->assertEquals(1, $point->getLng()); + $this->assertSame(2.0, $point->getLat()); + $this->assertSame(1.0, $point->getLng()); + $this->assertSame(4326, $point->getSrid()); } public function testToWKT() { - $point = new Point(1, 2); + $point = new Point(1, 2, 4326); - $this->assertEquals('POINT(2 1)', $point->toWKT()); + $this->assertSame('POINT(2 1)', $point->toWKT()); } public function testGettersAndSetters() @@ -25,30 +26,35 @@ public function testGettersAndSetters() $point = new Point(1, 2); $this->assertSame(1.0, $point->getLat()); $this->assertSame(2.0, $point->getLng()); + $this->assertSame(0, $point->getSrid()); $point->setLat('3'); $point->setLng('4'); + $point->setSrid(100); $this->assertSame(3.0, $point->getLat()); $this->assertSame(4.0, $point->getLng()); + $this->assertSame(100, $point->getSrid()); } public function testPair() { - $point = Point::fromPair('1.5 2'); + $point = Point::fromPair('1.5 2', 4326); $this->assertSame(1.5, $point->getLng()); $this->assertSame(2.0, $point->getLat()); + $this->assertSame(4326, $point->getSrid()); $this->assertSame('1.5 2', $point->toPair()); } public function testToString() { - $point = Point::fromString('1.3 2'); + $point = Point::fromString('1.3 2', 4326); $this->assertSame(1.3, $point->getLng()); $this->assertSame(2.0, $point->getLat()); + $this->assertSame(4326, $point->getSrid()); $this->assertEquals('1.3 2', (string) $point); } @@ -57,13 +63,16 @@ public function testFromJson() { $point = Point::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); $this->assertInstanceOf(Point::class, $point); - $this->assertEquals(1.2, $point->getLat()); - $this->assertEquals(3.4, $point->getLng()); + $this->assertSame(1.2, $point->getLat()); + $this->assertSame(3.4, $point->getLng()); } public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + 'Expected GeoJson\Geometry\Point, got GeoJson\Geometry\LineString' + ); Point::fromJson('{"type": "LineString","coordinates":[[1,1],[2,2]]}'); } diff --git a/tests/Unit/Types/PolygonTest.php b/tests/Unit/Types/PolygonTest.php index de923c2f..aaab437b 100644 --- a/tests/Unit/Types/PolygonTest.php +++ b/tests/Unit/Types/PolygonTest.php @@ -20,12 +20,12 @@ protected function setUp() ] ); - $this->polygon = new Polygon([$collection]); + $this->polygon = new Polygon([$collection], 4326); } public function testFromWKT() { - $polygon = Polygon::fromWKT('POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))'); + $polygon = Polygon::fromWKT('POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))', 4326); $this->assertInstanceOf(Polygon::class, $polygon); $this->assertEquals(2, $polygon->count()); @@ -56,7 +56,10 @@ public function testFromJson() public function testInvalidGeoJsonException() { - $this->setExpectedException(\Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class); + $this->assertException( + \Grimzy\LaravelMysqlSpatial\Exceptions\InvalidGeoJsonException::class, + 'Expected GeoJson\Geometry\Polygon, got GeoJson\Geometry\Point' + ); Polygon::fromJson('{"type":"Point","coordinates":[3.4,1.2]}'); }