From d3438e3039b22be67f6f94ee027abffa806ea6cf Mon Sep 17 00:00:00 2001 From: Dan Hunsaker Date: Sun, 30 Dec 2018 15:39:06 -0700 Subject: [PATCH] Initial tests --- .gitignore | 4 + .scrutinizer.yml | 35 + .travis.yml | 67 + README.md | 19 +- composer.json | 13 +- composer.lock | 2026 ----------------- phpcs.xml | 7 + phpunit.xml.dist | 42 + src/Doctrine/Geometry.php | 3 +- src/Doctrine/GeometryCollection.php | 3 +- src/Doctrine/LineString.php | 3 +- src/Doctrine/MultiLineString.php | 3 +- src/Doctrine/MultiPoint.php | 3 +- src/Doctrine/MultiPolygon.php | 3 +- src/Doctrine/Point.php | 3 +- src/Doctrine/Polygon.php | 3 +- src/Eloquent/Builder.php | 6 +- src/Eloquent/SpatialTrait.php | 234 +- .../UnknownSpatialRelationFunction.php | 7 + src/MysqlConnection.php | 1 + src/Schema/Blueprint.php | 31 +- src/Schema/Grammars/PostgresGrammar.php | 33 +- src/SpatialServiceProvider.php | 5 +- tests/Integration/Migrations/CreateTables.php | 45 + tests/Integration/Migrations/UpdateTables.php | 62 + tests/Integration/Models/GeometryModel.php | 34 + .../Models/NoSpatialFieldsModel.php | 18 + tests/Integration/MysqlTest.php | 377 +++ tests/Integration/PostgresTest.php | 29 + tests/Integration/Tools/ClassFinder.php | 154 ++ tests/Makefile | 73 + tests/Unit/BaseTestCase.php | 19 + .../Unit/Connectors/ConnectionFactoryTest.php | 43 + tests/Unit/Eloquent/BuilderTest.php | 102 + tests/Unit/Eloquent/SpatialTraitTest.php | 577 +++++ tests/Unit/MysqlConnectionTest.php | 23 + tests/Unit/PostgresConnectionTest.php | 23 + tests/Unit/Schema/BlueprintTest.php | 120 + tests/Unit/Schema/BuilderTest.php | 39 + .../Unit/Schema/Grammars/MySqlGrammarTest.php | 122 + .../Schema/Grammars/PostgresGrammarTest.php | 124 + tests/Unit/Stubs/PDOStub.php | 10 + tests/bootstrap.php | 30 + tests/wait_container | 22 + 44 files changed, 2513 insertions(+), 2087 deletions(-) create mode 100644 .scrutinizer.yml create mode 100644 .travis.yml delete mode 100644 composer.lock create mode 100644 phpcs.xml create mode 100644 phpunit.xml.dist create mode 100644 src/Exceptions/UnknownSpatialRelationFunction.php create mode 100644 tests/Integration/Migrations/CreateTables.php create mode 100644 tests/Integration/Migrations/UpdateTables.php create mode 100644 tests/Integration/Models/GeometryModel.php create mode 100644 tests/Integration/Models/NoSpatialFieldsModel.php create mode 100644 tests/Integration/MysqlTest.php create mode 100644 tests/Integration/PostgresTest.php create mode 100644 tests/Integration/Tools/ClassFinder.php create mode 100644 tests/Makefile create mode 100644 tests/Unit/BaseTestCase.php create mode 100644 tests/Unit/Connectors/ConnectionFactoryTest.php create mode 100644 tests/Unit/Eloquent/BuilderTest.php create mode 100644 tests/Unit/Eloquent/SpatialTraitTest.php create mode 100644 tests/Unit/MysqlConnectionTest.php create mode 100644 tests/Unit/PostgresConnectionTest.php create mode 100644 tests/Unit/Schema/BlueprintTest.php create mode 100644 tests/Unit/Schema/BuilderTest.php create mode 100644 tests/Unit/Schema/Grammars/MySqlGrammarTest.php create mode 100644 tests/Unit/Schema/Grammars/PostgresGrammarTest.php create mode 100644 tests/Unit/Stubs/PDOStub.php create mode 100644 tests/bootstrap.php create mode 100755 tests/wait_container diff --git a/.gitignore b/.gitignore index a5bf6a6..22296d0 100755 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties +composer.lock +boxfile.yml +build/ +tests/_?db-*/ diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..d09f774 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,35 @@ +build: + nodes: + analysis: + project_setup: + override: + - 'true' + tests: + override: + - php-scrutinizer-run + - command: phpcs-run + use_website_config: false + + tests: + environment: + docker: true + php: + version: 7.2 + dependencies: + before: + - sudo service mysql stop + - sudo service postgresql stop + - make -C tests/ start_db_mysql start_db_postgres oV=8.0 pV=11 + tests: + override: + - command: ./vendor/bin/phpunit --coverage-clover=my-coverage-file + coverage: + file: my-coverage-file + format: php-clover + +checks: + php: true + +filter: + excluded_paths: + - tests/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1523830 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ +language: php + +sudo: required + +services: + - docker + +php: + - '5.6' + - '7.0' + - '7.1' + - '7.2' + - '7.3' + - hhvm + - nightly + +env: + - MYSQL_VERSION=5.7 PG_VERSION=9.1 + - MYSQL_VERSION=8.0 PG_VERSION=9.2 + - MARIA_VERSION=5.5 PG_VERSION=9.3 + - MARIA_VERSION=10.0 PG_VERSION=9.4 + - MARIA_VERSION=10.1 PG_VERSION=9.5 + - MARIA_VERSION=10.2 PG_VERSION=9.6 + - MARIA_VERSION=10.3 PG_VERSION=10 + - MARIA_VERSION=10.4 PG_VERSION=11 + +matrix: + allow_failures: + - php: '5.6' + - php: hhvm + - php: nightly + +before_install: + - sudo /etc/init.d/mysql stop + - | + if [ -n "${MYSQL_VERSION}" ] + then + make -C tests/ start_db_mysql oV=${MYSQL_VERSION} + else + make -C tests/ start_db_maria mV=${MARIA_VERSION} + fi + - sudo /etc/init.d/postgresql stop + - make -C tests/ start_db_postgres pV=${PG_VERSION} + +install: + - travis_retry composer install + +before_script: + - mkdir -p build/logs + - echo -n Ensuring MySQL/MariaDB is ready; ./tests/wait_container spatial-mysql ; echo + - echo -n Ensuring PostgreSQL DB is ready; ./tests/wait_container spatial-postgres ; echo + +script: + - vendor/bin/phpunit --coverage-clover build/logs/clover.xml + +after_script: + - | + if [ -n "${MYSQL_VERSION}" ] + then + make -C tests/ rm_db_mysql oV=${MYSQL_VERSION} + else + make -C tests/ rm_db_maria mV=${MARIA_VERSION} + fi + - make -C tests/ rm_db_postgres pV=${PG_VERSION} + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 6e17167..69a0234 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ # Laravel Spatial extension -This package is fully untested, undocumented and unstable and is a combination of the two great packages: +[![](https://img.shields.io/packagist/l/apptimists/laravel-spatial.svg?style=flat-square)](https://packagist.org/packages/apptimists/laravel-spatial) +[![](https://img.shields.io/packagist/php-v/apptimists/laravel-spatial.svg?style=flat-square)](https://packagist.org/packages/apptimists/laravel-spatial) +[![](https://img.shields.io/packagist/v/apptimists/laravel-spatial.svg?style=flat-square)](https://packagist.org/packages/apptimists/laravel-spatial) +[![](https://img.shields.io/packagist/dt/apptimists/laravel-spatial.svg?style=flat-square)](https://packagist.org/packages/apptimists/laravel-spatial) + +[![](https://img.shields.io/travis/apptimists/laravel-spatial.svg?style=flat-square)](https://github.com/apptimists/laravel-spatial) +[![](https://img.shields.io/codecov/c/github/apptimists/laravel-spatial.svg?style=flat-square)](https://codecov.io/gh/apptimists/laravel-spatial) +[![](https://img.shields.io/scrutinizer/g/apptimists/laravel-spatial.svg?style=flat-square)](https://scrutinizer-ci.com/g/apptimists/laravel-spatial/) + +This package is fully undocumented and unstable, and is a combination of the two great packages: - [Laravel PostGIS extension](https://github.com/njbarrett/laravel-postgis) - [Laravel MySQL spatial extension](https://github.com/grimzy/laravel-mysql-spatial) @@ -24,13 +33,13 @@ If you try using it on a shared host which is not fulfilling those requirements, We use the [GeoJson PHP Library](http://jmikola.github.io/geojson/) for describing spatial fields as GeoJSON object, e.g.: ``` use GeoJSON\Geometry\Point; - + ... - + $eloquent = new MyModel(); $eloquent->location = new Point([49.7, 6.9]); - + ... - + $eloquent->save(); ``` diff --git a/composer.json b/composer.json index 9184f50..7b2ebce 100755 --- a/composer.json +++ b/composer.json @@ -11,11 +11,14 @@ ], "require": { "php": ">=5.6.4", + "doctrine/dbal": "^2.5", + "funiq/geophp": "dev-master", "illuminate/database": "^5.3", - "jmikola/geojson": "^1.0", - "phayes/geophp": "^1.2" + "jmikola/geojson": "^1.0" }, "require-dev": { + "laravel/browser-kit-testing": "^2.0", + "laravel/laravel": "^5.3", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~5.0" }, @@ -24,6 +27,12 @@ "LaravelSpatial\\": "src/" } }, + "autoload-dev": { + "classmap": [ + "tests/Unit", + "tests/Integration" + ] + }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 411ce7f..0000000 --- a/composer.lock +++ /dev/null @@ -1,2026 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "content-hash": "b626ea4ba0a5337f082eb6601770692e", - "packages": [ - { - "name": "doctrine/inflector", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "inflection", - "pluralize", - "singularize", - "string" - ], - "time": "2015-11-06T14:35:42+00:00" - }, - { - "name": "illuminate/container", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/illuminate/container.git", - "reference": "50aa19491d478edd907d1f67e0928944e8b2dcb5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/50aa19491d478edd907d1f67e0928944e8b2dcb5", - "reference": "50aa19491d478edd907d1f67e0928944e8b2dcb5", - "shasum": "" - }, - "require": { - "illuminate/contracts": "5.4.*", - "php": ">=5.6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Container\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Container package.", - "homepage": "https://laravel.com", - "time": "2017-04-16T13:32:45+00:00" - }, - { - "name": "illuminate/contracts", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/illuminate/contracts.git", - "reference": "ab2825726bee46a67c8cc66789852189dbef74a9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/ab2825726bee46a67c8cc66789852189dbef74a9", - "reference": "ab2825726bee46a67c8cc66789852189dbef74a9", - "shasum": "" - }, - "require": { - "php": ">=5.6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Contracts\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Contracts package.", - "homepage": "https://laravel.com", - "time": "2017-03-29T13:17:47+00:00" - }, - { - "name": "illuminate/database", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/illuminate/database.git", - "reference": "890564c6b84bcb2b45d41d3da072fabf422c07f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/890564c6b84bcb2b45d41d3da072fabf422c07f5", - "reference": "890564c6b84bcb2b45d41d3da072fabf422c07f5", - "shasum": "" - }, - "require": { - "illuminate/container": "5.4.*", - "illuminate/contracts": "5.4.*", - "illuminate/support": "5.4.*", - "nesbot/carbon": "~1.20", - "php": ">=5.6.4" - }, - "suggest": { - "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", - "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", - "illuminate/console": "Required to use the database commands (5.4.*).", - "illuminate/events": "Required to use the observers with Eloquent (5.4.*).", - "illuminate/filesystem": "Required to use the migrations (5.4.*).", - "illuminate/pagination": "Required to paginate the result set (5.4.*)." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Database\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Database package.", - "homepage": "https://laravel.com", - "keywords": [ - "database", - "laravel", - "orm", - "sql" - ], - "time": "2017-04-11T22:53:18+00:00" - }, - { - "name": "illuminate/support", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/illuminate/support.git", - "reference": "b8cb37e15331c59da51c8ee5838038baa22d7955" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/b8cb37e15331c59da51c8ee5838038baa22d7955", - "reference": "b8cb37e15331c59da51c8ee5838038baa22d7955", - "shasum": "" - }, - "require": { - "doctrine/inflector": "~1.0", - "ext-mbstring": "*", - "illuminate/contracts": "5.4.*", - "paragonie/random_compat": "~1.4|~2.0", - "php": ">=5.6.4" - }, - "replace": { - "tightenco/collect": "self.version" - }, - "suggest": { - "illuminate/filesystem": "Required to use the composer class (5.2.*).", - "symfony/process": "Required to use the composer class (~3.2).", - "symfony/var-dumper": "Required to use the dd function (~3.2)." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Support\\": "" - }, - "files": [ - "helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Support package.", - "homepage": "https://laravel.com", - "time": "2017-04-09T14:34:57+00:00" - }, - { - "name": "jmikola/geojson", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/jmikola/geojson.git", - "reference": "6ec3016cc0215667b7775f6ead7bd0337ad66eee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmikola/geojson/zipball/6ec3016cc0215667b7775f6ead7bd0337ad66eee", - "reference": "6ec3016cc0215667b7775f6ead7bd0337ad66eee", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~3.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "GeoJson\\": "src/" - }, - "classmap": [ - "stubs/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - } - ], - "description": "GeoJSON implementation for PHP", - "homepage": "https://github.com/jmikola/geojson", - "keywords": [ - "geo", - "geojson", - "geospatial" - ], - "time": "2015-09-27T15:35:21+00:00" - }, - { - "name": "nesbot/carbon", - "version": "1.22.1", - "source": { - "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", - "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "symfony/translation": "~2.6 || ~3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2", - "phpunit/phpunit": "~4.0 || ~5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.23-dev" - } - }, - "autoload": { - "psr-4": { - "Carbon\\": "src/Carbon/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Nesbitt", - "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" - } - ], - "description": "A simple API extension for DateTime.", - "homepage": "http://carbon.nesbot.com", - "keywords": [ - "date", - "datetime", - "time" - ], - "time": "2017-01-16T07:55:07+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v2.0.10", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "pseudorandom", - "random" - ], - "time": "2017-03-13T16:27:32+00:00" - }, - { - "name": "phayes/geophp", - "version": "1.2", - "source": { - "type": "git", - "url": "https://github.com/phayes/geoPHP.git", - "reference": "015404e85b602e0df1f91441f8db0f9e98f7e567" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phayes/geoPHP/zipball/015404e85b602e0df1f91441f8db0f9e98f7e567", - "reference": "015404e85b602e0df1f91441f8db0f9e98f7e567", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "4.1.*" - }, - "type": "library", - "autoload": { - "classmap": [ - "geoPHP.inc" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2 or New-BSD" - ], - "authors": [ - { - "name": "Patrick Hayes" - } - ], - "description": "GeoPHP is a open-source native PHP library for doing geometry operations. It is written entirely in PHP and can therefore run on shared hosts. It can read and write a wide variety of formats: WKT (including EWKT), WKB (including EWKB), GeoJSON, KML, GPX, GeoRSS). It works with all Simple-Feature geometries (Point, LineString, Polygon, GeometryCollection etc.) and can be used to get centroids, bounding-boxes, area, and a wide variety of other useful information.", - "homepage": "https://github.com/phayes/geoPHP", - "time": "2014-12-02T06:11:22+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2016-11-14T01:06:16+00:00" - }, - { - "name": "symfony/translation", - "version": "v3.2.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "f4a04d2df710f81515df576b2de06bdeee518b83" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83", - "reference": "f4a04d2df710f81515df576b2de06bdeee518b83", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/config": "<2.8" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/intl": "^2.8.18|^3.2.5", - "symfony/yaml": "~2.8|~3.0" - }, - "suggest": { - "psr/log": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Translation Component", - "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" - } - ], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14T21:17:01+00:00" - }, - { - "name": "hamcrest/hamcrest-php", - "version": "v1.2.2", - "source": { - "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" - }, - "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "satooshi/php-coveralls": "dev-master" - }, - "type": "library", - "autoload": { - "classmap": [ - "hamcrest" - ], - "files": [ - "hamcrest/Hamcrest.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD" - ], - "description": "This is the PHP port of Hamcrest Matchers", - "keywords": [ - "test" - ], - "time": "2015-05-11T14:41:42+00:00" - }, - { - "name": "mockery/mockery", - "version": "0.9.9", - "source": { - "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "6fdb61243844dc924071d3404bb23994ea0b6856" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856", - "reference": "6fdb61243844dc924071d3404bb23994ea0b6856", - "shasum": "" - }, - "require": { - "hamcrest/hamcrest-php": "~1.1", - "lib-pcre": ">=7.0", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.9.x-dev" - } - }, - "autoload": { - "psr-0": { - "Mockery": "library/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" - } - ], - "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/padraic/mockery", - "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" - ], - "time": "2017-02-28T12:52:32+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2017-04-12T18:52:22+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "1.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2015-12-27T11:43:31+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.2.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "time": "2016-11-25T06:54:22+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2017-03-02T20:05:34+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "^1.0 || ^2.0" - }, - "require-dev": { - "ext-xdebug": "^2.1.4", - "phpunit/phpunit": "^5.7" - }, - "suggest": { - "ext-xdebug": "^2.5.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2017-04-02T07:44:40+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2016-10-03T07:40:28+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.9", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2017-02-26T11:10:40+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.11", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2017-02-27T10:12:30+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "5.7.19", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1", - "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.4", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "^1.2.4", - "sebastian/diff": "~1.2", - "sebastian/environment": "^1.3.4 || ^2.0", - "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "~2.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0.3|~2.0", - "symfony/yaml": "~2.1|~3.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.7.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2017-04-03T02:22:27+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "3.4.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2 || ^2.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2016-12-08T20:27:08+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" - }, - { - "name": "sebastian/comparator", - "version": "1.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2017-01-29T09:50:25+00:00" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08T07:14:41+00:00" - }, - { - "name": "sebastian/environment", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-11-26T07:53:53+00:00" - }, - { - "name": "sebastian/exporter", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2016-11-19T08:54:04+00:00" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12T03:26:01+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", - "shasum": "" - }, - "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-02-18T15:18:39+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19T07:33:16+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "symfony/yaml", - "version": "v3.2.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "require-dev": { - "symfony/console": "~2.8|~3.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2017-05-01T14:55:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2016-11-23T20:04:58+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=5.6.4" - }, - "platform-dev": [] -} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..b8b688f --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,7 @@ + + + . + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..e15cb4f --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,42 @@ + + + + + ./tests/Unit + + + ./tests/Integration + + + + + + + + + ./src + + + + + + + + + + + + + + diff --git a/src/Doctrine/Geometry.php b/src/Doctrine/Geometry.php index b3d556f..540de89 100755 --- a/src/Doctrine/Geometry.php +++ b/src/Doctrine/Geometry.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class Geometry extends Type { @@ -11,7 +12,7 @@ class Geometry extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'geometry'; + return \DB::connection()->getSchemaGrammar()->typeGeometry(new Fluent); } public function getName() diff --git a/src/Doctrine/GeometryCollection.php b/src/Doctrine/GeometryCollection.php index f095f7c..41435f6 100755 --- a/src/Doctrine/GeometryCollection.php +++ b/src/Doctrine/GeometryCollection.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class GeometryCollection extends Type { @@ -11,7 +12,7 @@ class GeometryCollection extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'geometrycollection'; + return \DB::connection()->getSchemaGrammar()->typeGeometrycollection(new Fluent); } public function getName() diff --git a/src/Doctrine/LineString.php b/src/Doctrine/LineString.php index a92e075..30a2acb 100755 --- a/src/Doctrine/LineString.php +++ b/src/Doctrine/LineString.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class LineString extends Type { @@ -11,7 +12,7 @@ class LineString extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'linestring'; + return \DB::connection()->getSchemaGrammar()->typeLinestring(new Fluent); } public function getName() diff --git a/src/Doctrine/MultiLineString.php b/src/Doctrine/MultiLineString.php index c4b1f5a..2653de9 100755 --- a/src/Doctrine/MultiLineString.php +++ b/src/Doctrine/MultiLineString.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class MultiLineString extends Type { @@ -11,7 +12,7 @@ class MultiLineString extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'multilinestring'; + return \DB::connection()->getSchemaGrammar()->typeMultilinestring(new Fluent); } public function getName() diff --git a/src/Doctrine/MultiPoint.php b/src/Doctrine/MultiPoint.php index e8147bb..2bc1701 100755 --- a/src/Doctrine/MultiPoint.php +++ b/src/Doctrine/MultiPoint.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class MultiPoint extends Type { @@ -11,7 +12,7 @@ class MultiPoint extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'multipoint'; + return \DB::connection()->getSchemaGrammar()->typeMultipoint(new Fluent); } public function getName() diff --git a/src/Doctrine/MultiPolygon.php b/src/Doctrine/MultiPolygon.php index cb95b5a..1c74510 100755 --- a/src/Doctrine/MultiPolygon.php +++ b/src/Doctrine/MultiPolygon.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class MultiPolygon extends Type { @@ -11,7 +12,7 @@ class MultiPolygon extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'multipolygon'; + return \DB::connection()->getSchemaGrammar()->typeMultipolygon(new Fluent); } public function getName() diff --git a/src/Doctrine/Point.php b/src/Doctrine/Point.php index bf466f5..f4f261d 100755 --- a/src/Doctrine/Point.php +++ b/src/Doctrine/Point.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class Point extends Type { @@ -11,7 +12,7 @@ class Point extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'point'; + return \DB::connection()->getSchemaGrammar()->typePoint(new Fluent); } public function getName() diff --git a/src/Doctrine/Polygon.php b/src/Doctrine/Polygon.php index cb898df..7239c74 100755 --- a/src/Doctrine/Polygon.php +++ b/src/Doctrine/Polygon.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use Illuminate\Support\Fluent; class Polygon extends Type { @@ -11,7 +12,7 @@ class Polygon extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { - return 'polygon'; + return \DB::connection()->getSchemaGrammar()->typePolygon(new Fluent); } public function getName() diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index 78b97b0..7c69671 100755 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -3,7 +3,7 @@ namespace LaravelSpatial\Eloquent; use GeoJson\Geometry\Geometry; -use geoPHP; +use geoPHP\geoPHP; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; class Builder extends EloquentBuilder @@ -12,8 +12,8 @@ public function update(array $values) { foreach ($values as $key => &$value) { if ($value instanceof Geometry) { - $geometry = geoPHP::load((object)$value->jsonSerialize(), 'json'); - $value = $this->getQuery()->raw('ST_GeomFromText(\'' . $geometry->out('wkt') . '\')'); + $wkt = geoPHP::load(json_decode(json_encode($value->jsonSerialize()), false), 'json')->out('wkt'); + $value = $this->getQuery()->raw("ST_GeomFromText('{$wkt}')"); } } diff --git a/src/Eloquent/SpatialTrait.php b/src/Eloquent/SpatialTrait.php index 415efa4..82e4fe5 100755 --- a/src/Eloquent/SpatialTrait.php +++ b/src/Eloquent/SpatialTrait.php @@ -4,15 +4,55 @@ use GeoJson\GeoJson; use GeoJSON\Geometry\Geometry; -use geoPHP; +use geoPHP\geoPHP; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +use Illuminate\Database\MySqlConnection; +use Illuminate\Database\PostgresConnection; use LaravelSpatial\Exceptions\SpatialFieldsNotDefinedException; +use LaravelSpatial\Exceptions\UnknownSpatialRelationFunction; - +/** + * Trait SpatialTrait. + * + * @property array $attributes + * @method static distance($geometryColumn, $geometry, $distance) + * @method static distanceExcludingSelf($geometryColumn, $geometry, $distance) + * @method static distanceSphere($geometryColumn, $geometry, $distance) + * @method static distanceSphereExcludingSelf($geometryColumn, $geometry, $distance) + * @method static comparison($geometryColumn, $geometry, $relationship) + * @method static within($geometryColumn, $polygon) + * @method static crosses($geometryColumn, $geometry) + * @method static contains($geometryColumn, $geometry) + * @method static disjoint($geometryColumn, $geometry) + * @method static equals($geometryColumn, $geometry) + * @method static intersects($geometryColumn, $geometry) + * @method static overlaps($geometryColumn, $geometry) + * @method static doesTouch($geometryColumn, $geometry) + */ trait SpatialTrait { + /* + * The attributes that are spatial representations. + * To use this Trait, add the following array to the model class + * + * @var array + * + * protected $spatialFields = []; + */ + public $geometries = []; + protected $stRelations = [ + 'Within', + 'Crosses', + 'Contains', + 'Disjoint', + 'Equals', + 'Intersects', + 'Overlaps', + 'Touches', + ]; + /** * Create a new Eloquent query builder for the model. * @@ -31,13 +71,17 @@ public function setRawAttributes(array $attributes, $sync = false) foreach ($attributes as $attribute => &$value) { if (in_array($attribute, $spatial_fields) && is_string($value) && strlen($value) >= 15) { // MySQL adds 4 NULL bytes at the start of the binary - $prefix = "\0\0\0\0"; - if (substr($value, 0, strlen($prefix)) == $prefix) { - $value = substr($value, strlen($prefix)); + if ($this->getConnection() instanceof MySqlConnection && substr($value, 0, 4) == "\0\0\0\0") { + $value = substr($value, 4); + } elseif ($this->getConnection() instanceof PostgresConnection) { + $value = pack('H*', $value); } - $geometry = geoPHP::load($value, 'wkb'); - $value = GeoJson::jsonUnserialize(json_decode($geometry->out('json'))); + try { + $value = GeoJson::jsonUnserialize(json_decode(geoPHP::load($value, 'wkb')->out('json'))); + } catch (\Exception $e) { + throw new \Exception("Can't parse WKB {$value}: {$e->getMessage()}", $e->getCode(), $e); + } } } @@ -46,20 +90,25 @@ public function setRawAttributes(array $attributes, $sync = false) public function getSpatialFields() { - if (property_exists($this, 'spatialFields')) { + if (property_exists($this, 'spatialFields') && !empty($this->spatialFields)) { return $this->spatialFields; } else { throw new SpatialFieldsNotDefinedException(__CLASS__ . ' has to define $spatialFields'); } } + protected function toWkt(Geometry $value) + { + return ($this->getConnection() instanceof PostgresConnection ? 'SRID=4326;' : '') . + geoPHP::load(json_decode(json_encode($value->jsonSerialize()), false), 'json')->out('wkt'); + } + protected function performInsert(EloquentBuilder $query, array $options = []) { foreach ($this->attributes as $key => $value) { - if ($value instanceof Geometry) { - $geometry = geoPHP::load((object)$value->jsonSerialize(), 'json'); + if ($value instanceof Geometry && $this->isColumnAllowed($key)) { $this->geometries[$key] = $value; // Preserve the geometry objects prior to the insert - $this->attributes[$key] = $this->getConnection()->raw('ST_GeomFromText(\'' . $geometry->out('wkt') . '\')'); + $this->attributes[$key] = $this->getConnection()->raw("ST_GeomFromText('{$this->toWkt($value)}')"); } } @@ -71,4 +120,167 @@ protected function performInsert(EloquentBuilder $query, array $options = []) return $insert; // Return the result of the parent insert } + + public function isColumnAllowed($geometryColumn) + { + if (!in_array($geometryColumn, $this->getSpatialFields())) { + throw new SpatialFieldsNotDefinedException(); + } + + return true; + } + + public function scopeDistance($query, $geometryColumn, $geometry, $distance) + { + if ($this->isColumnAllowed($geometryColumn)) { + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) <= ?", [ + $this->toWkt($geometry), + $distance, + ]); + } + + return $query; + } + + public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $distance) + { + if ($this->isColumnAllowed($geometryColumn)) { + $query = $this->scopeDistance($query, $geometryColumn, $geometry, $distance); + + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) != 0", [ + $this->toWkt($geometry), + ]); + } + + return $query; + } + + public function scopeDistanceValue($query, $geometryColumn, $geometry) + { + if ($this->isColumnAllowed($geometryColumn)) { + $columns = $query->getQuery()->columns; + + if (!$columns) { + $query->select('*'); + } + + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->selectRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) as distance", [ + $this->toWkt($geometry), + ]); + } + + return $query; + } + + public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distance) + { + $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere'; + + if ($this->isColumnAllowed($geometryColumn)) { + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) <= ?", [ + $this->toWkt($geometry), + $distance, + ]); + } + + return $query; + } + + public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geometry, $distance) + { + $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere'; + + if ($this->isColumnAllowed($geometryColumn)) { + $query = $this->scopeDistanceSphere($query, $geometryColumn, $geometry, $distance); + + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) != 0", [ + $this->toWkt($geometry), + ]); + } + + return $query; + } + + public function scopeDistanceSphereValue($query, $geometryColumn, $geometry) + { + $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere'; + + if ($this->isColumnAllowed($geometryColumn)) { + $columns = $query->getQuery()->columns; + + if (!$columns) { + $query->select('*'); + } + + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->selectRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) as distance", [ + $this->toWkt($geometry), + ]); + } + + return $query; + } + + public function scopeComparison($query, $geometryColumn, $geometry, $relationship) + { + if ($this->isColumnAllowed($geometryColumn)) { + $relationship = ucfirst(strtolower($relationship)); + + if (!in_array($relationship, $this->stRelations)) { + throw new UnknownSpatialRelationFunction($relationship); + } + + $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; + $query->whereRaw("ST_{$relationship}(`{$geometryColumn}`, ST_GeomFromText(?))", [ + $this->toWkt($geometry), + ]); + } + + return $query; + } + + public function scopeWithin($query, $geometryColumn, $polygon) + { + return $this->scopeComparison($query, $geometryColumn, $polygon, 'within'); + } + + public function scopeCrosses($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'crosses'); + } + + public function scopeContains($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'contains'); + } + + public function scopeDisjoint($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'disjoint'); + } + + public function scopeEquals($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'equals'); + } + + public function scopeIntersects($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'intersects'); + } + + public function scopeOverlaps($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'overlaps'); + } + + public function scopeDoesTouch($query, $geometryColumn, $geometry) + { + return $this->scopeComparison($query, $geometryColumn, $geometry, 'touches'); + } } diff --git a/src/Exceptions/UnknownSpatialRelationFunction.php b/src/Exceptions/UnknownSpatialRelationFunction.php new file mode 100644 index 0000000..6a937f6 --- /dev/null +++ b/src/Exceptions/UnknownSpatialRelationFunction.php @@ -0,0 +1,7 @@ +getDoctrineSchemaManager()->getDatabasePlatform(); diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index a86bae6..973070b 100755 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -21,9 +21,9 @@ public function geometry($column) * @param $column * @return \Illuminate\Support\Fluent */ - public function point($column) + public function point($column, $srid = null) { - return $this->addColumn('point', $column); + return $this->addColumn('point', $column, compact('srid')); } /** @@ -101,7 +101,7 @@ public function geometryCollection($column) */ public function spatialIndex($columns, $name = null) { - return $this->indexCommand('spatial', $columns, $name); + return $this->indexCommand('spatialIndex', $columns, $name); } /** @@ -110,8 +110,29 @@ public function spatialIndex($columns, $name = null) * @param string|array $index * @return \Illuminate\Support\Fluent */ - public function dropSpatial($index) + public function dropSpatialIndex($index) { - return $this->dropIndexCommand('dropIndex', 'spatial', $index); + return $this->dropIndexCommand('dropIndex', 'spatialIndex', $index); + } + + /** + * Enable postgis on this database. + * Will create the extension in the database. + * + * @return \Illuminate\Support\Fluent + */ + public function enablePostgis() + { + return $this->addCommand('enablePostgis'); + } + + /** + * Disable postgis on this database. + * WIll drop the extension in the database. + * @return \Illuminate\Support\Fluent + */ + public function disablePostgis() + { + return $this->addCommand('disablePostgis'); } } diff --git a/src/Schema/Grammars/PostgresGrammar.php b/src/Schema/Grammars/PostgresGrammar.php index ab58ba5..9b2f1b1 100755 --- a/src/Schema/Grammars/PostgresGrammar.php +++ b/src/Schema/Grammars/PostgresGrammar.php @@ -85,42 +85,25 @@ public function typeGeography(Fluent $column) } /** - * Adds a statement to add a geometrycollection geometry column + * Adds a statement to add a geometry geometry column * - * @param Blueprint $blueprint - * @param Fluent $command + * @param Fluent $column * @return string */ - public function compileGeometrycollection(Blueprint $blueprint, Fluent $command) + public function typeGeometry(Fluent $column) { - $command->type = 'GEOMETRYCOLLECTION'; - - return $this->compileGeometry($blueprint, $command); + return 'GEOGRAPHY(GEOMETRY, 4326)'; } /** - * Adds a statement to add a geometry column + * Adds a statement to add a geometrycollection geometry column * - * @param Blueprint $blueprint - * @param Fluent $command + * @param Fluent $column * @return string */ - protected function compileGeometry(Blueprint $blueprint, Fluent $command) + public function typeGeometrycollection(Fluent $column) { - - $dimensions = $command->dimensions ?: 2; - $typmod = $command->typmod ? 'true' : 'false'; - $srid = $command->srid ?: 4326; - - return sprintf( - "SELECT AddGeometryColumn('%s', '%s', %d, '%s', %d, %s)", - $blueprint->getTable(), - $command->column, - $srid, - strtoupper($command->type), - $dimensions, - $typmod - ); + return 'GEOGRAPHY(GEOMETRYCOLLECTION, 4326)'; } /** diff --git a/src/SpatialServiceProvider.php b/src/SpatialServiceProvider.php index e0465a3..4f764a8 100755 --- a/src/SpatialServiceProvider.php +++ b/src/SpatialServiceProvider.php @@ -32,14 +32,14 @@ public function register() // The connection factory is used to create the actual connection instances on // the database. We will inject the factory into the manager so that it may // make the connections while they are actually needed and not of before. - $this->app->singleton('db.factory', function ($app) { + $this->app->singleton('db.factory', function($app) { return new ConnectionFactory($app); }); // The database manager is used to resolve various connections, since multiple // connections might be managed. It also implements the connection resolver // interface which may be used by other components requiring connections. - $this->app->singleton('db', function ($app) { + $this->app->singleton('db', function($app) { return new DatabaseManager($app, $app['db.factory']); }); @@ -53,6 +53,7 @@ public function register() 'multipoint' => MultiPoint::class, 'multilinestring' => MultiLineString::class, 'multipolygon' => MultiPolygon::class, + 'geomcollection' => GeometryCollection::class, 'geometrycollection' => GeometryCollection::class, ]; $typeNames = array_keys(Type::getTypesMap()); diff --git a/tests/Integration/Migrations/CreateTables.php b/tests/Integration/Migrations/CreateTables.php new file mode 100644 index 0000000..42df622 --- /dev/null +++ b/tests/Integration/Migrations/CreateTables.php @@ -0,0 +1,45 @@ +increments('id'); + $table->geometry('geo')->default(null)->nullable(); + $table->point('location'); // required to be not null in order to add an index + $table->lineString('line')->default(null)->nullable(); + $table->polygon('shape')->default(null)->nullable(); + $table->multiPoint('multi_locations')->default(null)->nullable(); + $table->multiLineString('multi_lines')->default(null)->nullable(); + $table->multiPolygon('multi_shapes')->default(null)->nullable(); + $table->geometryCollection('multi_geometries')->default(null)->nullable(); + $table->timestamps(); + }); + + Schema::create('no_spatial_fields', function(Blueprint $table) { + $table->increments('id'); + $table->geometry('geometry')->default(null)->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('no_spatial_fields'); + Schema::drop('test_geometries'); + } +} diff --git a/tests/Integration/Migrations/UpdateTables.php b/tests/Integration/Migrations/UpdateTables.php new file mode 100644 index 0000000..1571ed0 --- /dev/null +++ b/tests/Integration/Migrations/UpdateTables.php @@ -0,0 +1,62 @@ +point('location')->change(); + + // The other field changes are just here to test if change works with them, we're not changing anything + $table->geometry('geo')->default(null)->nullable()->change(); + $table->lineString('line')->default(null)->nullable()->change(); + $table->polygon('shape')->default(null)->nullable()->change(); + $table->multiPoint('multi_locations')->default(null)->nullable()->change(); + $table->multiLineString('multi_lines')->default(null)->nullable()->change(); + $table->multiPolygon('multi_shapes')->default(null)->nullable()->change(); + $table->geometryCollection('multi_geometries')->default(null)->nullable()->change(); + + }); + + if (is_a(\DB::connection(), MysqlConnection::class)) { + // MySQL < 5.7.5: table has to be MyISAM + \DB::statement('ALTER TABLE test_geometries ENGINE = MyISAM'); + } + + Schema::table('test_geometries', function(Blueprint $table) { + // Add a spatial index on the location field + $table->spatialIndex('location'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('test_geometries', function(Blueprint $table) { + $table->dropSpatialIndex(['location']); // either an array of column names or the index name + }); + + if (is_a(\DB::connection(), MysqlConnection::class)) { + \DB::statement('ALTER TABLE test_geometries ENGINE = InnoDB'); + } + + Schema::table('test_geometries', function(Blueprint $table) { + $table->point('location')->nullable()->change(); + }); + } +} diff --git a/tests/Integration/Models/GeometryModel.php b/tests/Integration/Models/GeometryModel.php new file mode 100644 index 0000000..2995f7c --- /dev/null +++ b/tests/Integration/Models/GeometryModel.php @@ -0,0 +1,34 @@ +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', '127.0.0.1')); + $app['config']->set('database.connections.mysql.database', 'spatial_test'); + $app['config']->set('database.connections.mysql.username', 'root'); + $app['config']->set('database.connections.mysql.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 + protected 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 { + /** @scrutinizer ignore-deprecated */ + $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); + } + } + + public function testSpatialFieldsNotDefinedException() + { + $this->assertException(\LaravelSpatial\Exceptions\SpatialFieldsNotDefinedException::class); + + $geo = new NoSpatialFieldsModel(); + $geo->geometry = new Point([1, 2]); + $geo->save(); + + NoSpatialFieldsModel::all(); + } + + public function testInsertPoint() + { + $geo = new GeometryModel(); + $geo->location = new Point([1, 2]); + $geo->save(); + $this->assertDatabaseHas($geo->getTable(), ['id' => $geo->id]); + } + + public function testInsertLineString() + { + $geo = new GeometryModel(); + + $geo->location = new Point([1, 2]); + $geo->line = new LineString([new Point([1, 1]), new Point([2, 2])]); + $geo->save(); + $this->assertDatabaseHas($geo->getTable(), ['id' => $geo->id]); + } + + public function testInsertPolygon() + { + $geo = new GeometryModel(); + + $geo->location = new Point([1, 2]); + $geo->shape = new Polygon([[[0, 10], [10, 10], [10, 0], [0, 0], [0, 10]]]); + $geo->save(); + $this->assertDatabaseHas($geo->getTable(), ['id' => $geo->id]); + } + + public function testInsertMultiPoint() + { + $geo = new GeometryModel(); + + $geo->location = new Point([1, 2]); + $geo->multi_locations = new MultiPoint([new Point([1, 1]), new Point([2, 2])]); + $geo->save(); + $this->assertDatabaseHas($geo->getTable(), ['id' => $geo->id]); + } + + public function testInsertMultiPolygon() + { + $geo = new GeometryModel(); + + $geo->location = new Point([1, 2]); + + $geo->multi_shapes = new MultiPolygon([ + new Polygon([[[0, 10], [10, 10], [10, 0], [0, 0], [0, 10]]]), + new Polygon([[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]), + ]); + $geo->save(); + $this->assertDatabaseHas($geo->getTable(), ['id' => $geo->id]); + } + + public function testInsertGeometryCollection() + { + $geo = new GeometryModel(); + + $geo->location = new Point([1, 2]); + + $geo->multi_geometries = new GeometryCollection([ + new Polygon([[[0, 10], [10, 10], [10, 0], [0, 0], [0, 10]]]), + new Polygon([[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]), + new Point([0, 0]), + ]); + $geo->save(); + $this->assertDatabaseHas($geo->getTable(), ['id' => $geo->id]); + } + + public function testUpdate() + { + $geo = new GeometryModel(); + $geo->location = new Point([1, 2]); + $geo->save(); + + $to_update = GeometryModel::all()->first(); + $to_update->location = new Point([2, 3]); + $to_update->save(); + + $this->assertDatabaseHas($geo->getTable(), ['id' => $to_update->id]); + + $all = GeometryModel::all(); + $this->assertCount(1, $all); + + $updated = $all->first(); + $this->assertInstanceOf(Point::class, $updated->location); + $this->assertEquals(2, $updated->location->getCoordinates()[0]); + $this->assertEquals(3, $updated->location->getCoordinates()[1]); + } + + public function testDistance() + { + $loc1 = new GeometryModel(); + $loc1->location = new Point([1, 1]); + $loc1->save(); + + $loc2 = new GeometryModel(); + $loc2->location = new Point([2, 2]); // Distance from loc1: 1.4142135623731 + $loc2->save(); + + $loc3 = new GeometryModel(); + $loc3->location = new Point([3, 3]); // Distance from loc1: 2.8284271247462 + $loc3->save(); + + $a = GeometryModel::distance('location', $loc1->location, 2)->get(); + $this->assertCount(2, $a); + $this->assertTrue($a->contains('location', $loc1->location)); + $this->assertTrue($a->contains('location', $loc2->location)); + $this->assertFalse($a->contains('location', $loc3->location)); + + // Excluding self + $b = GeometryModel::distanceExcludingSelf('location', $loc1->location, 2)->get(); + $this->assertCount(1, $b); + $this->assertFalse($b->contains('location', $loc1->location)); + $this->assertTrue($b->contains('location', $loc2->location)); + $this->assertFalse($b->contains('location', $loc3->location)); + + $c = GeometryModel::distance('location', $loc1->location, 1)->get(); + $this->assertCount(1, $c); + $this->assertTrue($c->contains('location', $loc1->location)); + $this->assertFalse($c->contains('location', $loc2->location)); + $this->assertFalse($c->contains('location', $loc3->location)); + } + + public function testDistanceSphere() + { + try { + $loc1 = new GeometryModel(); + $loc1->location = new Point([-73.971732, 40.767864]); + $loc1->save(); + + $loc2 = new GeometryModel(); + $loc2->location = new Point([-73.971271, 40.767664]); // Distance from loc1: 44.741406484588 + $loc2->save(); + + $loc3 = new GeometryModel(); + $loc3->location = new Point([-73.977619, 40.761434]); // Distance from loc1: 870.06424066202 + $loc3->save(); + + $a = GeometryModel::distanceSphere('location', $loc1->location, 200)->get(); + $this->assertCount(2, $a); + $this->assertTrue($a->contains('location', $loc1->location)); + $this->assertTrue($a->contains('location', $loc2->location)); + $this->assertFalse($a->contains('location', $loc3->location)); + + // Excluding self + $b = GeometryModel::distanceSphereExcludingSelf('location', $loc1->location, 200)->get(); + $this->assertCount(1, $b); + $this->assertFalse($b->contains('location', $loc1->location)); + $this->assertTrue($b->contains('location', $loc2->location)); + $this->assertFalse($b->contains('location', $loc3->location)); + + if ($this->is_postgres || $this->after_fix) { + $c = GeometryModel::distanceSphere('location', $loc1->location, 44.741406484236)->get(); + } else { + $c = GeometryModel::distanceSphere('location', $loc1->location, 44.741406484587)->get(); + } + $this->assertCount(1, $c); + $this->assertTrue($c->contains('location', $loc1->location)); + $this->assertFalse($c->contains('location', $loc2->location)); + $this->assertFalse($c->contains('location', $loc3->location)); + } catch (\Illuminate\Database\QueryException $e) { + if (strpos($e->getMessage(), 'FUNCTION spatial_test.ST_Distance_Sphere does not exist') > -1) { + $this->markTestSkipped('Spherical distance tests [distanceSphere*()] not supported on the current DBMS'); + } + throw $e; + } + } + + public function testDistanceValue() + { + $loc1 = new GeometryModel(); + $loc1->location = new Point([1, 1]); + $loc1->save(); + + $loc2 = new GeometryModel(); + $loc2->location = new Point([2, 2]); // Distance from loc1: 1.4142135623731 + $loc2->save(); + + $a = GeometryModel::distanceValue('location', $loc1->location)->get(); + $this->assertCount(2, $a); + $this->assertEquals(0, $a[0]->distance); + $this->assertEquals(1.4142135623, $a[1]->distance); // PHP floats' 11th+ digits don't matter + } + + public function testDistanceSphereValue() + { + try { + $loc1 = new GeometryModel(); + $loc1->location = new Point([-73.971732, 40.767864]); + $loc1->save(); + + $loc2 = new GeometryModel(); + $loc2->location = new Point([-73.971271, 40.767664]); // Distance from loc1: 44.741406484236 + $loc2->save(); + + $a = GeometryModel::distanceSphereValue('location', $loc1->location)->get(); + $this->assertCount(2, $a); + $this->assertEquals(0, $a[0]->distance); + + if ($this->is_postgres) { + $this->assertEquals("44.7415664", number_format($a[1]->distance, 7)); // Postgres calculates this differently? + } elseif ($this->after_fix) { + $this->assertEquals(44.7414064842, $a[1]->distance); // PHP floats' 11th+ digits don't matter + } else { + $this->assertEquals(44.7414064845, $a[1]->distance); // PHP floats' 11th+ digits don't matter + } + } catch (\Illuminate\Database\QueryException $e) { + if (strpos($e->getMessage(), 'FUNCTION spatial_test.ST_Distance_Sphere does not exist') > -1) { + $this->markTestSkipped('Spherical distance tests [distanceSphere*()] not supported on the current DBMS'); + } + throw $e; + } + } + + //public function testBounding() { + // $point = new Point([0, 0]); + // + // $linestring1 = \GeoJson\Geometry\LineString::fromWkt("LINESTRING(1 1, 2 2)"); + // $linestring2 = \GeoJson\Geometry\LineString::fromWkt("LINESTRING(20 20, 24 24)"); + // $linestring3 = \GeoJson\Geometry\LineString::fromWkt("LINESTRING(0 10, 10 10)"); + // + // $geo1 = new GeometryModel(); + // $geo1->location = $point; + // $geo1->line = $linestring1; + // $geo1->save(); + // + // $geo2 = new GeometryModel(); + // $geo2->location = $point; + // $geo2->line = $linestring2; + // $geo2->save(); + // + // $geo3 = new GeometryModel(); + // $geo3->location = $point; + // $geo3->line = $linestring3; + // $geo3->save(); + // + // $polygon = new Polygon([[[0, 10],[10, 10],[10, 0],[0, 0],[0, 10]]]); + // + // $result = GeometryModel::Bounding($polygon, 'line')->get(); + // $this->assertCount(2, $result); + // $this->assertTrue($result->contains($geo1)); + // $this->assertFalse($result->contains($geo2)); + // $this->assertTrue($result->contains($geo3)); + // + //} +} diff --git a/tests/Integration/PostgresTest.php b/tests/Integration/PostgresTest.php new file mode 100644 index 0000000..1f7921a --- /dev/null +++ b/tests/Integration/PostgresTest.php @@ -0,0 +1,29 @@ +set('database.default', 'pgsql'); + $app['config']->set('database.connections.pgsql.host', env('DB_HOST', '127.0.0.1')); + $app['config']->set('database.connections.pgsql.database', 'spatial_test'); + $app['config']->set('database.connections.pgsql.username', 'postgres'); + $app['config']->set('database.connections.pgsql.password', ''); + + return $app; + } + + protected function isMySQL8AfterFix() + { + return false; + } +} diff --git a/tests/Integration/Tools/ClassFinder.php b/tests/Integration/Tools/ClassFinder.php new file mode 100644 index 0000000..5bc7caf --- /dev/null +++ b/tests/Integration/Tools/ClassFinder.php @@ -0,0 +1,154 @@ +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/Makefile b/tests/Makefile new file mode 100644 index 0000000..7663c04 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,73 @@ +mySrv=mysql + +oV=5.7 +oDB_DIR=$(shell pwd)/_odb-$(oV) + +mV=10.3 +mDB_DIR=$(shell pwd)/_mdb-$(mV) + +pV=10 +pDB_DIR=$(shell pwd)/_pdb-$(pV) + +start_db_mysql: + @echo Starting MySQL $(oV) + docker run --rm -d --name spatial-mysql \ + -p 3306:3306 \ + -v $(oDB_DIR):/var/lib/mysql \ + --health-cmd='mysqladmin ping --silent && mysql -e "use spatial_test;"' \ + -e MYSQL_DATABASE=spatial_test \ + -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ + mysql:$(oV) --character-set-server=utf8 --collation-server=utf8_general_ci --default-authentication-plugin=mysql_native_password + +start_db_maria: + @echo Starting MariaDB $(mV) + docker run --rm -d --name spatial-mysql \ + -p 3306:3306 \ + -v $(mDB_DIR):/var/lib/mysql \ + --health-cmd='mysqladmin ping --silent && mysql -e "use spatial_test;"' \ + -e MYSQL_DATABASE=spatial_test \ + -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ + mariadb:$(mV) --character-set-server=utf8 --collation-server=utf8_general_ci + +start_db_postgres: + @echo Starting Postgres $(pV) + docker run --rm -d --name spatial-postgres \ + -p 5432:5432 \ + -v $(pDB_DIR):/var/lib/postgresql/data \ + --health-cmd='if [[ $(pV) -ge 9.3 ]]; then pg_isready -U postgres; else psql -U postgres -c "SELECT 1"; fi' \ + -e POSTGRES_DB=spatial_test \ + -e POSTGRES_PASSWORD="" \ + mdillon/postgis:$(pV) + +rm_db_mysql: + docker stop spatial-mysql || true + sudo rm -Rf $(oDB_DIR) + +rm_db_maria: + docker stop spatial-mysql || true + sudo rm -Rf $(mDB_DIR) + +rm_db_postgres: + docker stop spatial-postgres || true + sudo rm -Rf $(pDB_DIR) + +refresh_db_mysql: rm_db_mysql start_db_mysql + +refresh_db_maria: rm_db_maria start_db_maria + +refresh_db_postgres: rm_db_postgres start_db_postgres + +get_ip_mysql: + @docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' spatial-mysql + +get_ip_postgres: + @docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' spatial-postgres + +run_tests: start_db_${mySrv} start_db_postgres __do_test rm_db_${mySrv} rm_db_postgres + +__do_test: + @echo -n "\nWaiting for servers to fully start" + @./wait_container spatial-mysql + @./wait_container spatial-postgres + @echo " Ready! Starting tests.\n" + @cd .. && ./vendor/bin/phpunit || true diff --git a/tests/Unit/BaseTestCase.php b/tests/Unit/BaseTestCase.php new file mode 100644 index 0000000..769b4f8 --- /dev/null +++ b/tests/Unit/BaseTestCase.php @@ -0,0 +1,19 @@ +setExpectedException($exceptionName); + } + } +} diff --git a/tests/Unit/Connectors/ConnectionFactoryTest.php b/tests/Unit/Connectors/ConnectionFactoryTest.php new file mode 100644 index 0000000..cbcfbb2 --- /dev/null +++ b/tests/Unit/Connectors/ConnectionFactoryTest.php @@ -0,0 +1,43 @@ +makePartial(); + $factory->shouldAllowMockingProtectedMethods(); + $conn = $factory->createConnection('mysql', $pdo, 'database'); + + $this->assertInstanceOf(MysqlConnection::class, $conn); + } + + public function testPostgresMakeCallsCreateConnection() + { + $pdo = new PDOStub(); + + $factory = Mockery::mock(ConnectionFactory::class, [new Container()])->makePartial(); + $factory->shouldAllowMockingProtectedMethods(); + $conn = $factory->createConnection('pgsql', $pdo, 'database'); + + $this->assertInstanceOf(PostgresConnection::class, $conn); + } + + public function testCreateConnectionDifferentDriver() + { + $pdo = new PDOStub(); + + $factory = Mockery::mock(ConnectionFactory::class, [new Container()])->makePartial(); + $factory->shouldAllowMockingProtectedMethods(); + $conn = $factory->createConnection('sqlite', $pdo, 'database'); + + $this->assertInstanceOf(\Illuminate\Database\SQLiteConnection::class, $conn); + } +} diff --git a/tests/Unit/Eloquent/BuilderTest.php b/tests/Unit/Eloquent/BuilderTest.php new file mode 100644 index 0000000..3ff4740 --- /dev/null +++ b/tests/Unit/Eloquent/BuilderTest.php @@ -0,0 +1,102 @@ +makePartial(); + $grammar = Mockery::mock(MySqlGrammar::class)->makePartial(); + $this->queryBuilder = Mockery::mock(QueryBuilder::class, [$connection, $grammar]); + + $this->queryBuilder + ->shouldReceive('from') + ->once() + ->andReturn($this->queryBuilder); + + $this->queryBuilder + ->shouldReceive('take') + ->with(1) + ->andReturn($this->queryBuilder); + + $this->queryBuilder + ->shouldReceive('get') + ->andReturn([]); + + $this->builder = new Builder($this->queryBuilder); + $this->builder->setModel(new TestBuilderModel()); + } + + public function testUpdatePoint() + { + $point = new Point([1, 2]); + + $this->queryBuilder + ->shouldReceive('raw') + ->with("ST_GeomFromText('POINT (1 2)')") + ->andReturn(new Expression("ST_GeomFromText('POINT (1 2)')")); + + $this->queryBuilder + ->shouldReceive('update') + ->andReturn(1); + + $this->builder->update(['point' => $point]); + } + + public function testUpdateLinestring() + { + $linestring = new LineString([new Point([0, 0]), new Point([1, 1]), new Point([2, 2])]); + + $this->queryBuilder + ->shouldReceive('raw') + ->with("ST_GeomFromText('LINESTRING (0 0, 1 1, 2 2)')") + ->andReturn(new Expression("ST_GeomFromText('LINESTRING (0 0, 1 1, 2 2)')")); + + $this->queryBuilder + ->shouldReceive('update') + ->andReturn(1); + + $this->builder->update(['linestring' => $linestring]); + } + + public function testUpdatePolygon() + { + $polygon = new Polygon([[new Point([0, 0]), new Point([0, 1]), new Point([1, 1]), new Point([0, 0])]]); + + $this->queryBuilder + ->shouldReceive('raw') + ->with("ST_GeomFromText('POLYGON ((0 0, 0 1, 1 1, 0 0))')") + ->andReturn(new Expression("ST_GeomFromText('POLYGON ((0 0, 0 1, 1 1, 0 0))')")); + + $this->queryBuilder + ->shouldReceive('update') + ->andReturn(1); + + $this->builder->update(['polygon' => $polygon]); + } +} + +class TestBuilderModel extends Model +{ + use SpatialTrait; + + public $timestamps = false; + protected $spatialFields = ['point', 'linestring', 'polygon']; +} diff --git a/tests/Unit/Eloquent/SpatialTraitTest.php b/tests/Unit/Eloquent/SpatialTraitTest.php new file mode 100644 index 0000000..b6888af --- /dev/null +++ b/tests/Unit/Eloquent/SpatialTraitTest.php @@ -0,0 +1,577 @@ +model = new TestModel(); + $this->queries = &$this->model->getConnection()->getPdo()->queries; + } + + public function tearDown() + { + $this->model->getConnection()->getPdo()->resetQueries(); + } + + public function testInsertUpdatePointHasCorrectSql() + { + $this->assertFalse($this->model->exists); + + $this->model->point = new Point([1, 2]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`point`) values (ST_GeomFromText('POINT (1 2)'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->point = new Point([1, 2]); + $this->model->save(); + + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `point` = ST_GeomFromText('POINT (1 2)') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testInsertUpdateLineStringHasCorrectSql() + { + $point1 = new Point([1, 2]); + $point2 = new Point([2, 3]); + + $this->assertFalse($this->model->exists); + + $this->model->linestring = new LineString([$point1, $point2]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`linestring`) values (ST_GeomFromText('LINESTRING (1 2, 2 3)'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->linestring = new LineString([$point1, $point2]); + $this->model->save(); + + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `linestring` = ST_GeomFromText('LINESTRING (1 2, 2 3)') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testInsertUpdatePolygonHasCorrectSql() + { + $point1 = new Point([1, 2]); + $point2 = new Point([2, 3]); + $point3 = new Point([3, 2]); + $point4 = new Point([2, 1]); + + $this->assertFalse($this->model->exists); + + $this->model->polygon = new Polygon([[$point1, $point2, $point3, $point4, $point1]]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`polygon`) values (ST_GeomFromText('POLYGON ((1 2, 2 3, 3 2, 2 1, 1 2))'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->polygon = new Polygon([[$point1, $point2, $point3, $point4, $point1]]); + $this->model->save(); + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `polygon` = ST_GeomFromText('POLYGON ((1 2, 2 3, 3 2, 2 1, 1 2))') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testInsertUpdateMultiPointHasCorrectSql() + { + $point1 = new Point([1, 2]); + $point2 = new Point([2, 3]); + + $this->assertFalse($this->model->exists); + + $this->model->multipoint = new MultiPoint([$point1, $point2]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`multipoint`) values (ST_GeomFromText('MULTIPOINT ((1 2), (2 3))'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->multipoint = new MultiPoint([$point1, $point2]); + $this->model->save(); + + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `multipoint` = ST_GeomFromText('MULTIPOINT ((1 2), (2 3))') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testInsertUpdateMultiLineStringHasCorrectSql() + { + $point1 = new Point([1, 2]); + $point2 = new Point([2, 3]); + $linestring1 = new LineString([$point1, $point2]); + $point3 = new Point([3, 2]); + $point4 = new Point([2, 1]); + $linestring2 = new LineString([$point3, $point4]); + + $this->assertFalse($this->model->exists); + + $this->model->multilinestring = new MultiLineString([$linestring1, $linestring2]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`multilinestring`) values (ST_GeomFromText('MULTILINESTRING ((1 2, 2 3), (3 2, 2 1))'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->multilinestring = new MultiLineString([$linestring1, $linestring2]); + $this->model->save(); + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `multilinestring` = ST_GeomFromText('MULTILINESTRING ((1 2, 2 3), (3 2, 2 1))') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testInsertUpdateMultiPolygonHasCorrectSql() + { + $point1 = new Point([1, 2]); + $point2 = new Point([2, 3]); + $point3 = new Point([3, 2]); + $point4 = new Point([2, 1]); + $point5 = new Point([4, 5]); + $point6 = new Point([5, 6]); + $point7 = new Point([6, 5]); + $point8 = new Point([5, 4]); + + $polygon1 = new Polygon([[$point1, $point2, $point3, $point4, $point1]]); + $polygon2 = new Polygon([[$point5, $point6, $point7, $point8, $point5]]); + + $this->assertFalse($this->model->exists); + + $this->model->multipolygon = new MultiPolygon([$polygon1, $polygon2]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`multipolygon`) values (ST_GeomFromText('MULTIPOLYGON (((1 2, 2 3, 3 2, 2 1, 1 2)), ((4 5, 5 6, 6 5, 5 4, 4 5)))'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->multipolygon = new MultiPolygon([$polygon1, $polygon2]); + $this->model->save(); + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `multipolygon` = ST_GeomFromText('MULTIPOLYGON (((1 2, 2 3, 3 2, 2 1, 1 2)), ((4 5, 5 6, 6 5, 5 4, 4 5)))') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testInsertUpdateGeometryCollectionHasCorrectSql() + { + $point1 = new Point([1, 2]); + $linestring1 = new LineString([[2, 3], [3, 3]]); + + $this->assertFalse($this->model->exists); + + $this->model->geometrycollection = new GeometryCollection([$point1, $linestring1]); + $this->model->save(); + + $this->assertStringStartsWith('insert', $this->queries[0]); + $this->assertContains("insert into `test_models` (`geometrycollection`) values (ST_GeomFromText('GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (2 3, 3 3))'))", $this->queries[0]); + // TODO: assert bindings in query + $this->assertTrue($this->model->exists); + + $this->model->geometrycollection = new GeometryCollection([$point1, $linestring1]); + $this->model->save(); + $this->assertStringStartsWith('update', $this->queries[1]); + $this->assertContains("update `test_models` set `geometrycollection` = ST_GeomFromText('GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (2 3, 3 3))') where `id` = ?", $this->queries[1]); + // TODO: assert bindings in query + } + + public function testSettingRawAttributes() + { + $attributes['point'] = pack('H*', '0101000000000000000000f03f0000000000000040'); + + $this->model->setRawAttributes($attributes); + $this->assertInstanceOf(Point::class, ($this->model->point)); + } + + public function testSpatialFieldsNotDefinedException() + { + $model = new TestNoSpatialModel(); + $this->assertException(SpatialFieldsNotDefinedException::class); + $model->getSpatialFields(); + } + + public function testScopeDistance() + { + $point = new Point([1, 2]); + $query = TestModel::distance('point', $point, 10); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertEquals('ST_Distance(point, ST_GeomFromText(?)) <= ?', $q->wheres[0]['sql']); + $this->assertEquals('POINT (1 2)', $bindings[0]); + $this->assertEquals(10, $bindings[1]); + } + + public function testScopeDistanceExcludingSelf() + { + $point = new Point([1, 2]); + $query = TestModel::distanceExcludingSelf('point', $point, 10); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $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('POINT (1 2)', $bindings[0]); + $this->assertEquals(10, $bindings[1]); + $this->assertEquals('POINT (1 2)', $bindings[2]); + } + + public function testScopeDistanceSphere() + { + $point = new Point([1, 2]); + $query = TestModel::distanceSphere('point', $point, 10); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $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('POINT (1 2)', $bindings[0]); + $this->assertEquals(10, $bindings[1]); + } + + public function testScopeDistanceSphereExcludingSelf() + { + $point = new Point([1, 2]); + $query = TestModel::distanceSphereExcludingSelf('point', $point, 10); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $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('POINT (1 2)', $bindings[0]); + $this->assertEquals(10, $bindings[1]); + $this->assertEquals('POINT (1 2)', $bindings[2]); + } + + public function testScopeDistanceValue() + { + $point = new Point([1, 2]); + $query = TestModel::distanceValue('point', $point); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->columns); + $bindings = $q->getRawBindings()['select']; + $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('POINT (1 2)', $bindings[0]); + } + + public function testScopeDistanceValueWithSelect() + { + $point = new Point([1, 2]); + $query = TestModel::select('some_column')->distanceValue('point', $point); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->columns); + $bindings = $q->getRawBindings()['select']; + $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('POINT (1 2)', $bindings[0]); + } + + public function testScopeDistanceSphereValue() + { + $point = new Point([1, 2]); + $query = TestModel::distanceSphereValue('point', $point); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->columns); + $bindings = $q->getRawBindings()['select']; + $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('POINT (1 2)', $bindings[0]); + } + + public function testScopeDistanceSphereValueWithSelect() + { + $point = new Point([1, 2]); + $query = TestModel::select('some_column')->distanceSphereValue('point', $point); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->columns); + $bindings = $q->getRawBindings()['select']; + $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('POINT (1 2)', $bindings[0]); + } + + private function buildTestPolygon() + { + $point1 = new Point([1, 1]); + $point2 = new Point([1, 2]); + $point3 = new Point([1, 2]); + $point4 = new Point([2, 2]); + $point5 = new Point([2, 2]); + $point6 = new Point([1, 1]); + + return new Polygon([[$point1, $point2, $point3, $point4, $point5, $point6]]); + } + + public function testScopeComparison() + { + $query = TestModel::comparison('point', $this->buildTestPolygon(), 'within'); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Within(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeWithin() + { + $query = TestModel::within('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Within(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeCrosses() + { + $query = TestModel::crosses('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Crosses(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeContains() + { + $query = TestModel::contains('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Contains(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeDisjoint() + { + $query = TestModel::disjoint('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Disjoint(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeEquals() + { + $query = TestModel::equals('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Equals(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeIntersects() + { + $query = TestModel::intersects('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Intersects(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeOverlaps() + { + $query = TestModel::overlaps('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Overlaps(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } + + public function testScopeDoesTouch() + { + $query = TestModel::doesTouch('point', $this->buildTestPolygon()); + + $this->assertInstanceOf(Builder::class, $query); + $q = $query->getQuery(); + $this->assertNotEmpty($q->wheres); + $bindings = $q->getRawBindings()['where']; + $this->assertNotEmpty($bindings); + $this->assertContains('ST_Touches(`point`, ST_GeomFromText(?))', $q->wheres[0]['sql']); + $this->assertEquals('POLYGON ((1 1, 1 2, 1 2, 2 2, 2 2, 1 1))', $bindings[0]); + } +} + +/** + * Test Model + * + * @property $point + * @property $linestring + * @property $polygon + * @property $multipoint + * @property $multilinestring + * @property $multipolygon + * @property $geometrycollection + */ +class TestModel extends Model +{ + use SpatialTrait; + + protected $spatialFields = [ + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + 'geometrycollection', + ]; + + public $timestamps = false; + + public static $pdo; + + public static function resolveConnection($connection = null) + { + if (is_null(static::$pdo)) { + static::$pdo = Mockery::mock('TestPDO')->makePartial(); + } + + return new MysqlConnection(static::$pdo); + } + + public function testrelatedmodels() + { + return $this->hasMany(TestRelatedModel::class); + } + + public function testrelatedmodels2() + { + return $this->belongsToMany(TestRelatedModel::class); + } +} + +class TestRelatedModel extends TestModel +{ + public function testmodel() + { + return $this->belongsTo(TestModel::class); + } + + public function testmodels() + { + return $this->belongsToMany(TestModel::class); + } +} + +class TestNoSpatialModel extends Model +{ + use SpatialTrait; +} + +class TestPDO extends PDO +{ + public $queries = []; + + public $counter = 1; + + public function prepare($statement, $driver_options = []) + { + $this->queries[] = $statement; + + $stmt = Mockery::mock('PDOStatement'); + $stmt->shouldReceive('bindValue')->zeroOrMoreTimes(); + $stmt->shouldReceive('execute'); + $stmt->shouldReceive('fetchAll')->andReturn([['id' => 1, 'point' => 'POINT(1 2)']]); + $stmt->shouldReceive('rowCount')->andReturn(1); + + return $stmt; + } + + public function lastInsertId($name = null) + { + return $this->counter++; + } + + public function resetQueries() + { + $this->queries = []; + } +} diff --git a/tests/Unit/MysqlConnectionTest.php b/tests/Unit/MysqlConnectionTest.php new file mode 100644 index 0000000..2ca3a39 --- /dev/null +++ b/tests/Unit/MysqlConnectionTest.php @@ -0,0 +1,23 @@ + 'mysql', 'prefix' => 'prefix', 'database' => 'database', 'name' => 'foo']; + $this->mysqlConnection = new MysqlConnection(new PDOStub(), 'database', 'prefix', $mysqlConfig); + } + + public function testGetSchemaBuilder() + { + $builder = $this->mysqlConnection->getSchemaBuilder(); + + $this->assertInstanceOf(Builder::class, $builder); + } +} diff --git a/tests/Unit/PostgresConnectionTest.php b/tests/Unit/PostgresConnectionTest.php new file mode 100644 index 0000000..8a1e81e --- /dev/null +++ b/tests/Unit/PostgresConnectionTest.php @@ -0,0 +1,23 @@ + 'pgsql', 'prefix' => 'prefix', 'database' => 'database', 'name' => 'foo']; + $this->postgresConnection = new PostgresConnection(new PDOStub(), 'database', 'prefix', $pgConfig); + } + + public function testGetSchemaBuilder() + { + $builder = $this->postgresConnection->getSchemaBuilder(); + + $this->assertInstanceOf(Builder::class, $builder); + } +} diff --git a/tests/Unit/Schema/BlueprintTest.php b/tests/Unit/Schema/BlueprintTest.php new file mode 100644 index 0000000..18c9844 --- /dev/null +++ b/tests/Unit/Schema/BlueprintTest.php @@ -0,0 +1,120 @@ +blueprint = Mockery::mock(Blueprint::class) + ->makePartial()->shouldAllowMockingProtectedMethods(); + } + + public function testGeometry() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('geometry', 'col') + ->once(); + + $this->blueprint->geometry('col'); + } + + public function testPoint() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('point', 'col', ['srid' => null]) + ->once(); + + $this->blueprint->point('col'); + } + + public function testLinestring() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('linestring', 'col') + ->once(); + + $this->blueprint->linestring('col'); + } + + public function testPolygon() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('polygon', 'col') + ->once(); + + $this->blueprint->polygon('col'); + } + + public function testMultiPoint() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('multipoint', 'col') + ->once(); + + $this->blueprint->multipoint('col'); + } + + public function testMultiLineString() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('multilinestring', 'col') + ->once(); + + $this->blueprint->multilinestring('col'); + } + + public function testMulltiPolygon() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('multipolygon', 'col') + ->once(); + + $this->blueprint->multipolygon('col'); + } + + public function testGeometryCollection() + { + $this->blueprint + ->shouldReceive('addColumn') + ->with('geometrycollection', 'col') + ->once(); + + $this->blueprint->geometrycollection('col'); + } + + public function testEnablePostgis() + { + $this->blueprint + ->shouldReceive('addCommand') + ->with('enablePostgis') + ->once(); + + $this->blueprint->enablePostgis(); + } + + public function testDisablePostgis() + { + $this->blueprint + ->shouldReceive('addCommand') + ->with('disablePostgis') + ->once(); + + $this->blueprint->disablePostgis(); + } +} diff --git a/tests/Unit/Schema/BuilderTest.php b/tests/Unit/Schema/BuilderTest.php new file mode 100644 index 0000000..177d4fd --- /dev/null +++ b/tests/Unit/Schema/BuilderTest.php @@ -0,0 +1,39 @@ +shouldReceive('getSchemaGrammar')->once()->andReturn(null); + + $mock = Mockery::mock(Builder::class, [$connection]); + $mock->makePartial()->shouldAllowMockingProtectedMethods(); + $blueprint = $mock->createBlueprint('test', function() { + }); + + $this->assertInstanceOf(Blueprint::class, $blueprint); + } + + public function testReturnsCorrectBlueprintForPostgres() + { + $connection = Mockery::mock(PostgresConnection::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn(null); + + $mock = Mockery::mock(Builder::class, [$connection]); + $mock->makePartial()->shouldAllowMockingProtectedMethods(); + $blueprint = $mock->createBlueprint('test', function() { + }); + + $this->assertInstanceOf(Blueprint::class, $blueprint); + } +} diff --git a/tests/Unit/Schema/Grammars/MySqlGrammarTest.php b/tests/Unit/Schema/Grammars/MySqlGrammarTest.php new file mode 100644 index 0000000..4402b1e --- /dev/null +++ b/tests/Unit/Schema/Grammars/MySqlGrammarTest.php @@ -0,0 +1,122 @@ +geometry('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOMETRY', $statements[0]); + } + + public function testAddingPoint() + { + $blueprint = new Blueprint('test'); + $blueprint->point('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('POINT', $statements[0]); + } + + public function testAddingLinestring() + { + $blueprint = new Blueprint('test'); + $blueprint->linestring('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('LINESTRING', $statements[0]); + } + + public function testAddingPolygon() + { + $blueprint = new Blueprint('test'); + $blueprint->polygon('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('POLYGON', $statements[0]); + } + + public function testAddingMultipoint() + { + $blueprint = new Blueprint('test'); + $blueprint->multipoint('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('MULTIPOINT', $statements[0]); + } + + public function testAddingMultiLinestring() + { + $blueprint = new Blueprint('test'); + $blueprint->multilinestring('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('MULTILINESTRING', $statements[0]); + } + + public function testAddingMultiPolygon() + { + $blueprint = new Blueprint('test'); + $blueprint->multipolygon('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('MULTIPOLYGON', $statements[0]); + } + + public function testAddingGeometryCollection() + { + $blueprint = new Blueprint('test'); + $blueprint->geometrycollection('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOMETRYCOLLECTION', $statements[0]); + } + + public function testAddRemoveSpatialIndex() + { + $blueprint = new Blueprint('test'); + $blueprint->point('foo'); + $blueprint->spatialIndex('foo'); + $addStatements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(2, count($addStatements)); + $this->assertContains('alter table `test` add spatial index `test_foo_spatialindex`(`foo`)', $addStatements[1]); + + $blueprint->dropSpatialIndex(['foo']); + $blueprint->dropSpatialIndex('test_foo_spatialindex'); + $dropStatements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $expectedSql = 'alter table `test` drop index `test_foo_spatialindex`'; + $this->assertEquals(5, count($dropStatements)); + $this->assertContains($expectedSql, $dropStatements[3]); + $this->assertContains($expectedSql, $dropStatements[4]); + } + + /** + * @return Connection + */ + protected function getConnection() + { + return Mockery::mock(MysqlConnection::class); + } + + protected function getGrammar() + { + return new MySqlGrammar(); + } +} diff --git a/tests/Unit/Schema/Grammars/PostgresGrammarTest.php b/tests/Unit/Schema/Grammars/PostgresGrammarTest.php new file mode 100644 index 0000000..ab097c2 --- /dev/null +++ b/tests/Unit/Schema/Grammars/PostgresGrammarTest.php @@ -0,0 +1,124 @@ +point('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOGRAPHY(POINT, 4326)', $statements[0]); + } + + public function testAddingLinestring() + { + $blueprint = new Blueprint('test'); + $blueprint->linestring('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOGRAPHY(LINESTRING, 4326)', $statements[0]); + } + + public function testAddingPolygon() + { + $blueprint = new Blueprint('test'); + $blueprint->polygon('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOGRAPHY(POLYGON, 4326)', $statements[0]); + } + + public function testAddingMultipoint() + { + $blueprint = new Blueprint('test'); + $blueprint->multipoint('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOGRAPHY(MULTIPOINT, 4326)', $statements[0]); + } + + public function testAddingMultiLinestring() + { + $blueprint = new Blueprint('test'); + $blueprint->multilinestring('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOGRAPHY(MULTILINESTRING, 4326)', $statements[0]); + } + + public function testAddingMultiPolygon() + { + $blueprint = new Blueprint('test'); + $blueprint->multipolygon('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOGRAPHY(MULTIPOLYGON, 4326)', $statements[0]); + } + + public function testAddingGeometry() + { + $blueprint = new Blueprint('test'); + $blueprint->geometry('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOMETRY', strtoupper($statements[0])); + } + + public function testAddingGeometryCollection() + { + $blueprint = new Blueprint('test'); + $blueprint->geometrycollection('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('GEOMETRYCOLLECTION', strtoupper($statements[0])); + } + + public function testEnablePostgis() + { + $blueprint = new Blueprint('test'); + $blueprint->enablePostgis(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('CREATE EXTENSION postgis', $statements[0]); + } + + public function testDisablePostgis() + { + $blueprint = new Blueprint('test'); + $blueprint->disablePostgis(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals(1, count($statements)); + $this->assertContains('DROP EXTENSION postgis', $statements[0]); + } + + /** + * @return Connection + */ + protected function getConnection() + { + return Mockery::mock(PostgresConnection::class); + } + + protected function getGrammar() + { + return new PostgresGrammar(); + } +} diff --git a/tests/Unit/Stubs/PDOStub.php b/tests/Unit/Stubs/PDOStub.php new file mode 100644 index 0000000..99d2d80 --- /dev/null +++ b/tests/Unit/Stubs/PDOStub.php @@ -0,0 +1,10 @@ +&1 +} + +function waitContainer { + while STATUS=$(getContainerHealth $1); [[ "$STATUS" != '"healthy"' ]]; do + if [[ "$STATUS" == $'\nTemplate parsing error'* ]]; then + echo " $1 Failed!" + exit 255 + fi + if [[ "$STATUS" == '"unhealthy"' ]]; then + docker inspect --format "{{json .State }}" $1 2>&1 + else + echo -n . + fi + sleep 5 + done +} + +waitContainer $@