diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..60777e7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text=auto + +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.scrutinizer.yml export-ignore +/.travis.yml export-ignore +/phpunit.xml export-ignore +/tests export-ignore diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 0000000..d39428b --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,14 @@ +### General Information +GeocoderLaravel Version: +Laravel Version: +PHP Version: +Operating System and Version: + +### Issue Description +[describe what is going wrong in plain english] + +### Steps to Replicate +[please provide a list of specific steps that will allow me to replicate this issue] + +### Stack Trace +[if an error is occuring, provide the complete stack trace here] diff --git a/.gitignore b/.gitignore index 2c1fc0c..7cfd555 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor composer.phar composer.lock -.DS_Store \ No newline at end of file +.DS_Store +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index 3c4775f..a586f6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,16 @@ language: php php: - - 5.3 - - 5.4 - - 5.5 + - 7.3 + - 7.4 before_script: - - curl -sS http://getcomposer.org/installer | php - - php composer.phar install --dev --prefer-dist --no-interaction + - travis_retry composer self-update + - travis_retry composer install --no-interaction --prefer-source --dev -script: phpunit --coverage-text --coverage-clover ./build/logs/clover.xml +script: + - mkdir -p ./build/logs + - ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml -after_script: php vendor/bin/coveralls +after_success: + - travis_retry php vendor/bin/php-coveralls -v diff --git a/CHANGELOG.md b/CHANGELOG.md index a226023..bf9a76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,186 @@ -CHANGELOG -========= +# Geocoder for Laravel Changelog +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). -0.2.1 (xxxx-xx-xx) ------------------- +## [4.3.4] - 2020-06-21 +### Fixed +- non-caching declaration to only apply to current query. +- caching to take provider into account. -n/a +### Changed +- `getProvider()` method to no longer be deprecated, and instead return the + currently set provider, or if none set, the first configured provider. +## [4.3.3] - 2020-06-20 +### Added +- functionality to not cache requests by using `doNotCache()`. -0.2.0 (2013-11-16) ------------------- +## [4.3.0] - 2020-02-29 +### Added +- Laravel 7 compatibility. -* use Geocoder 2.3.x -* use config file -* use singleton instead of share -* improve tests +## [4.1.2] - 23 May 2019 +### Fixed +- initialization of geocoder adapter. +## [4.0.21] - 3 Nov 2018 +### Added +- `->toJson()` method when querying results. -0.1.0 (2013-09-16) ------------------- +## [4.0.10] - 1 Jul 2018 +### Changed +- service provider to register singleton and alias in `register()` method. -* add badges -* initial import +## [4.0.9] - 28 May 2018 +### Added +- class-name resolution from Service container, allowing for dependency + injection. + +## [4.0.8] - 25 Mar 2018 +### Added +- work-around for missing `config_path()` function in Lumen. + +## [4.0.7] - 25 Mar 2018 +### Added +- optional dedicated cache store. +- hashed cache keys and hash collision prevention. +- custom cache store configuration instructions. + +## [4.0.6] - 9 Feb 2018 +### Added +- Laravel 5.6 compatibility. + +## [4.0.5] - 14 Jan 2018 +### Fixed +- loading of GeoIP2 provider from within Chain provider. + +### Changed +- unit testing to use Orchstral Testbench. + +## [4.0.4] - 27 Dec 2017 +### Added +- environment variable configuration option in default config to set Google Maps Locale. +- documentation comments in configuration file. + +### Changed +- composer dependency version constraints for Laravel to be within a specific range, instead of open-ended. + +## [4.0.3] - 27 Oct 2017 +### Fixed +- cache duration to work on 32-bit systems. +- geocoder to not cache if no results are provided. + +## [4.0.2] - 2 Sep 2017 +### Fixed +- erroneous method `getProvider()` and marked it as deprecated. + +## [4.0.1] - 7 Aug 2017 +### Fixed +- missing PSR-7 dependency. + +## [4.0.0] - 3 Aug 2017 +### Added +- Laravel 5.5 package auto-discovery. + +### Fixed +- typo which caused cache to be in-effective. + +### Changed +- implemented geocoder-php 4.0.0. +- version to 4.0.0 instead of 2.0.0 to maintain major version parity with + parent package. +- composer dependencies to release versions. +- unit tests to pass. +- updated readme with some clarifying notes. May have to completely rewrite it + if it ends up being unclear. + +## [2.0.0-dev] - 23 Jun 2017 +### Fixed +- failing Travis builds due TLS resolution issues by changing to a different + geocoding provider that was failing said resolution during CURL requests. + +### Changed +- build and coverage badges back to Travis and Coveralls + +## [2.0.0-dev] - 18 Jun 2017 +### Added +- compatibility with Geocoder 4.0-dev. +- caching to `geocodeQuery()` and `reverseQuery()` methods. + +### Updated +- the geocoder `all()` method to be deprecated. Use `get()`. + +## [1.1.0] - 17 Jun 2017 +### Added +- caching functionality for `geocode()` and `reverse()` methods. +- `cache-duration` variable to geocoder config. + +## [1.0.2] - 20 Mar 2017 +### Added +- unit test for reverse-geocoding. + +## [1.0.1] - 30 Jan 2017 +### Removed +- minimum Laravel requirement of 5.3 (reverted back to 5.0, just in case it was working for someone, but only Laravel 5.3 and 5.4 are officially supported). + +## [1.0.0] - 30 Jan 2017 +### Changed +- minimum Laravel requirement to 5.3. + +## [1.0.0-RC1] - 13 Oct 2016 +### Added +- ability to dump results #16. +- ability to use multiple providers in addition to the chain provider #47. +- more integration tests. +- special aggregator that allows chaining of `geocode()` and other methods. + +### Changed +- README documentation. +- to use Geocoder 3.3.x. +- namespace to `Geocoder\Laravel\...`. +- service provider to auto-load the facade. +- config file format. +- geocoding commands necessary to obtain results (must use `->all()`, `->get()`, + or `->dump()`) after the respective command. +- the service provider architecture. + +### Fixed +- MaxMindBinary Provider being instantiated with an Adapter #24. +- GeoIP2 Provider being instantiated with a generic Adapter. + +## [0.6.0] +- TBD + +## [0.5.0] - 11 Mar 2015 +### Added +- code of conduct message. +- Laravel 5 compatibility [BC]. + +### Updated +- documentation. + +## [0.4.1] - 23 Jun 2014 +### Fixed +- the way to implode provider's arguments + unit tests. + +## [0.4.0] - 13 Apr 2014 +### Updated +- to use Geocoder 2.4.x. + +## [0.3.0] - 13 Apr 2014 +### Added +- support for Provider arguments (backwards-compatibility break). + +## [0.2.0] - 16 Nov 2013 +### Added +- config file. + +### Updated +- to use Geocoder 2.3.x. +- to use singleton instead of share. +- tests. + +## [0.1.0] - 16 Sep 2013 +### Added +- badges. +- initial package. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..651f002 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,44 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3f909ff --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +Contributing +============ + +First of all, **thank you** for contributing, **you are awesome**! + +Here are a few rules to follow in order to ease code reviews, and discussions before +maintainers accept and merge your work. + +You MUST follow the [PSR-1](http://www.php-fig.org/psr/psr-1/) and +[PSR-2](http://www.php-fig.org/psr/psr-2/). If you don't know about any of them, you +should really read the recommendations. Can't wait? Use the [PHP-CS-Fixer +tool](http://cs.sensiolabs.org/). + +You MUST run the test suite. + +You MUST write (or update) unit tests. + +You SHOULD write documentation. + +Please, write [commit messages that make +sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), +and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing) +before submitting your Pull Request. + +One may ask you to [squash your +commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) +too. This is used to "clean" your Pull Request before merging it (we don't want +commits such as `fix tests`, `fix 2`, `fix 3`, etc.). + +Thank you! diff --git a/LICENSE b/LICENSE index 543b395..7751db3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013 Antoine Corcy +Copyright (c) 2013-2015 Antoine Corcy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7b49492..268684c 100644 --- a/README.md +++ b/README.md @@ -1,127 +1,348 @@ -Geocoder for Lavarel 4 -====================== - -This package allows you to use [**Geocoder**](http://geocoder-php.org/Geocoder/) -in [**Laravel 4**](http://laravel.com/). - -[![Latest StableVersion](https://poser.pugx.org/toin0u/geocoder-laravel/v/stable.png)](https://packagist.org/packages/toin0u/geocoder-laravel) -[![Total Downloads](https://poser.pugx.org/toin0u/geocoder-laravel/downloads.png)](https://packagist.org/packages/toin0u/geocoder-laravel) -[![Build Status](https://secure.travis-ci.org/geocoder-php/GeocoderLaravel.png)](http://travis-ci.org/geocoder-php/GeocoderLaravel) -[![Coverage Status](https://coveralls.io/repos/geocoder-php/GeocoderLaravel/badge.png)](https://coveralls.io/r/geocoder-php/GeocoderLaravel) +[![Travis](https://img.shields.io/travis/geocoder-php/GeocoderLaravel.svg)](https://travis-ci.org/geocoder-php/GeocoderLaravel) +[![Scrutinizer](https://img.shields.io/scrutinizer/g/geocoder-php/GeocoderLaravel.svg)](https://scrutinizer-ci.com/g/geocoder-php/GeocoderLaravel/) +[![Coveralls](https://img.shields.io/coveralls/geocoder-php/GeocoderLaravel.svg)](https://coveralls.io/github/geocoder-php/GeocoderLaravel) +[![GitHub release](https://img.shields.io/github/release/geocoder-php/GeocoderLaravel.svg)](https://github.com/geocoder-php/GeocoderLaravel/releases) +[![Packagist](https://img.shields.io/packagist/dt/toin0u/geocoder-laravel.svg)](https://packagist.org/packages/toin0u/geocoder-laravel) -Installation ------------- +# Geocoder for Laravel -It can be found on [Packagist](https://packagist.org/packages/toin0u/geocoder-laravel). -The recommended way is through [composer](http://getcomposer.org). +> If you still use **Laravel 4**, please check out the `0.4.x` branch + [here](https://github.com/geocoder-php/GeocoderLaravel/tree/0.4.x). -Edit `composer.json` and add: +**Version 4.0.0 is a backwards-compatibility-breaking update. Please review + this documentation, especially the _Usage_ section before installing.** +This package allows you to use [**Geocoder**](http://geocoder-php.org/Geocoder/) + in [**Laravel 5**](http://laravel.com/). + +## Requirements +- PHP >= 7.1.3 +- Laravel >= 5.0 + +## Installation +1. Install the package via composer: + ```sh + composer require toin0u/geocoder-laravel + ``` + +2. **If you are running Laravel 5.5 (the package will be auto-discovered), skip + this step.** Find the `providers` array key in `config/app.php` and register + the **Geocoder Service Provider**: + ```php + // 'providers' => [ + Geocoder\Laravel\Providers\GeocoderService::class, + // ]; + ``` +3. **Optional** I recommend adding the following lines to your `composer.json` file to prevent stale caches when upgrading or updating the package, both in your live and dev environments: ```json -{ - "require": { - "toin0u/geocoder-laravel": "@stable" - } -} + "post-update-cmd": [ + "@php artisan cache:clear", + ], + "post-install-cmd": [ + "@php artisan cache:clear", + ] ``` -**Protip:** you should browse the -[`toin0u/geocoder-laravel`](https://packagist.org/packages/toin0u/geocoder-laravel) -page to choose a stable version to use, avoid the `@stable` meta constraint. +## Configuration +Pay special attention to the language and region values if you are using them. + For example, the GoogleMaps provider uses TLDs for region values, and the + following for language values: https://developers.google.com/maps/faq#languagesupport. + +Further, a special note on the GoogleMaps provider: if you are using an API key, + you must also use set HTTPS to true. (Best is to leave it true always, unless + there is a special requirement not to.) -And install dependencies: +See the [Geocoder documentation](http://geocoder-php.org/Geocoder/) for a list + of available adapters and providers. + +### Dedicated Cache Store *Recommended* +To implement the dedicated cache store, add another redis store entry in +`config/database.php`, something like the following: +```php + "redis" => [ + // ... + + "geocode-cache" => [ // choose an appropriate name + 'host' => env('REDIS_HOST', '192.168.10.10'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => 1, // be sure this number differs from your other redis databases + ], + ] +``` -```bash -$ curl -sS https://getcomposer.org/installer | php -$ php composer.phar install +You will also need to add an entry in `config/cache.php` to point to this redis +database: +```php + "stores" => [ + // ... + + "geocode" => [ + 'driver' => 'redis', + 'connection' => 'geocode-cache', + ], + ], ``` +Finally, configure Geocoder for Laravel to use this store. Edit +`config/geocoder.php`: +```php + "cache" => [ + "store" => "geocode", -Usage ------ + // ... + ], +``` -Find the `providers` key in `app/config/app.php` and register the **Geocoder Service Provider**. +#### Disabling Caching on a Query-Basis +You can disable caching on a query-by-query basis as needed, like so: +```php + $results = app("geocoder") + ->doNotCache() + ->geocode('Los Angeles, CA') + ->get(); +``` +### Providers +If you are upgrading and have previously published the geocoder config file, you + need to add the `cache-duration` variable, otherwise cache will be disabled + (it will default to a `0` cache duration). The default cache duration provided + by the config file is `999999999` seconds, essentially forever. + +By default, the configuration specifies a Chain provider, containing the + GoogleMaps provider for addresses as well as reverse lookups with lat/long, + and the GeoIP provider for IP addresses. The first to return a result + will be returned, and subsequent providers will not be executed. The default + config file is kept lean with only those two providers. + +However, you are free to add or remove providers as needed, both inside the + Chain provider, as well as along-side it. The following is the default + configuration provided by the package: ```php -'providers' => array( - // ... +use Geocoder\Provider\Chain\Chain; +use Geocoder\Provider\GeoPlugin\GeoPlugin; +use Geocoder\Provider\GoogleMaps\GoogleMaps; +use Http\Client\Curl\Client; + +return [ + + /* + |-------------------------------------------------------------------------- + | Cache Duration + |-------------------------------------------------------------------------- + | + | Specify the cache duration in seconds. The default approximates a forever + | cache, but there are certain issues with Laravel's forever caching + | methods that prevent us from using them in this project. + | + | Default: 9999999 (integer) + | + */ + 'cache-duration' => 9999999, + + /* + |-------------------------------------------------------------------------- + | Providers + |-------------------------------------------------------------------------- + | + | Here you may specify any number of providers that should be used to + | perform geocaching operations. The `chain` provider is special, + | in that it can contain multiple providers that will be run in + | the sequence listed, should the previous provider fail. By + | default the first provider listed will be used, but you + | can explicitly call subsequently listed providers by + | alias: `app('geocoder')->using('google_maps')`. + | + | Please consult the official Geocoder documentation for more info. + | https://github.com/geocoder-php/Geocoder#providers + | + */ + 'providers' => [ + Chain::class => [ + GoogleMaps::class => [ + env('GOOGLE_MAPS_LOCALE', 'us'), + env('GOOGLE_MAPS_API_KEY'), + ], + GeoPlugin::class => [], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Adapter + |-------------------------------------------------------------------------- + | + | You can specify which PSR-7-compliant HTTP adapter you would like to use. + | There are multiple options at your disposal: CURL, Guzzle, and others. + | + | Please consult the official Geocoder documentation for more info. + | https://github.com/geocoder-php/Geocoder#usage + | + | Default: Client::class (FQCN for CURL adapter) + | + */ + 'adapter' => Client::class, + + /* + |-------------------------------------------------------------------------- + | Reader + |-------------------------------------------------------------------------- + | + | You can specify a reader for specific providers, like GeoIp2, which + | connect to a local file-database. The reader should be set to an + | instance of the required reader class or an array containing the reader + | class and arguments. + | + | Please consult the official Geocoder documentation for more info. + | https://github.com/geocoder-php/geoip2-provider + | + | Default: null + | + */ + 'reader' => null, + +]; +``` - 'Toin0u\Geocoder\GeocoderServiceProvider', -) +### Adapters +By default we provide a CURL adapter to get you running out of the box. + However, if you have already installed Guzzle or any other PSR-7-compatible + HTTP adapter, you are encouraged to replace the CURL adapter with it. Please + see the [Geocoder Documentation](https://github.com/geocoder-php/Geocoder) for + specific implementation details. + +### Customization +If you would like to make changes to the default configuration, publish and + edit the configuration file: +```sh +php artisan vendor:publish --provider="Geocoder\Laravel\Providers\GeocoderService" --tag="config" ``` -Find the `aliases` key in `app/config/app.php` and register the **Geocoder Facade**. +## Usage +The service provider initializes the `geocoder` service, accessible via the + facade `Geocoder::...` or the application helper `app('geocoder')->...`. +### Geocoding Addresses +#### Get Collection of Addresses ```php -'aliases' => array( - // ... - - 'Geocoder' => 'Toin0u\Geocoder\GeocoderFacade', -) +app('geocoder')->geocode('Los Angeles, CA')->get(); ``` -Configuration -------------- +#### Get IP Address Information +```php +app('geocoder')->geocode('8.8.8.8')->get(); +``` -Publish and edit the configuration file +#### Reverse-Geocoding +```php +app('geocoder')->reverse(43.882587,-103.454067)->get(); +``` -```bash -$ php artisan config:publish toin0u/geocoder-laravel +#### Dumping Results +```php +app('geocoder')->geocode('Los Angeles, CA')->dump('kml'); ``` -The service provider creates the following services: +#### Dependency Injection +```php +use Geocoder\Laravel\ProviderAndDumperAggregator as Geocoder; -* `geocoder`: the Geocoder instance. -* `geocoder.provider`: the provider used by Geocoder. -* `geocoder.adapter`: the HTTP adapter used to get data from remotes APIs. +class GeocoderController extends Controller +{ + public function getGeocode(Geocoder $geocoder) + { + $geocoder->geocode('Los Angeles, CA')->get() + } +} +``` -By default, the `geocoder.provider` service uses FreeGeoIP and the `geocoder.adapter` service uses the cURL adapter. -Override these services to use the adapter/provider you want by editing -`app/config/packages/toin0u/geocoder-laravel/config.php`: +## Upgrading +Anytime you upgrade this package, please remember to clear your cache, to prevent incompatible cached responses when breaking changes are introduced (this should hopefully only be necessary in major versions): +```sh +php artisan cache:clear +``` -```php -return array( - 'provider' => 'Geocoder\Provider\GoogleMapsProvider', - 'adapter' => 'Geocoder\HttpAdapter\CurlHttpAdapter' -); +### 1.x to 4.x +Update your composer.json file: +```json + "toin0u/geocoder-laravel": "^4.0", ``` -See [the Geocoder documentation](http://geocoder-php.org/Geocoder/) for a list of available adapters and providers. +The one change to keep in mind here is that the results returned from + `Geocoder for Laravel` are now using the Laravel-native Collections class + instead of returning an instance of `AddressCollection`. This should provide + greater versatility in manipulation of the results, and be inline with + expectations for working with Laravel. The existing `AddressCollection` + methods should map straight over to Laravel's `Collection` methods. But be sure + to double-check your results, if you have been using `count()`, + `first()`, `isEmpty()`, `slice()`, `has()`, `get()`, or `all()` on your results. +Also, `getProviders()` now returns a Laravel Collection instead of an array. -Example with Facade -------------------- +**Alert:** if you have been using the `getIterator()` method, it is no longer + needed. Simply iterate over your results as you would any other Laravel + collection. -```php -getMessage(); -} -``` +**Deprecated:** + - the `all()` method on the geocoder is being deprecated in favor of using + `get()`, which will return a Laravel Collection. You can then run `all()` + on that. This method will be removed in version 5.0.0. + - the `getProvider()` method on the geocoder is being deprecated in favor of using + `getProviders()`, which will return a Laravel Collection. You can then run `first()` + on that to get the same result. This method will be removed in version 5.0.0. + +**Added:** this version introduces a new way to create more complex queries: + - geocodeQuery() + - reverseQuery() + + Please see the [Geocoder documentation](https://github.com/geocoder-php/Geocoder) + for more details. + +### 0.x to 1.x +If you are upgrading from a pre-1.x version of this package, please keep the + following things in mind: + +1. Update your composer.json file as follows: + + ```json + "toin0u/geocoder-laravel": "^1.0", + ``` +2. Remove your `config/geocoder.php` configuration file. (If you need to customize it, follow the configuration instructions below.) +3. Remove any Geocoder alias in the aliases section of your `config/app.php`. (This package auto-registers the aliases.) +4. Update the service provider entry in your `config/app.php` to read: -Changelog ---------- + ```php + Geocoder\Laravel\Providers\GeocoderService::class, + ``` -[See the CHANGELOG file](https://github.com/geocoder-php/GeocoderLaravel/blob/master/CHANGELOG.md) +5. If you are using the facade in your code, you have two options: + 1. Replace the facades `Geocoder::` (and remove the corresponding `use` statements) with `app('geocoder')->`. + 2. Update the `use` statements to the following: + ```php + use Geocoder\Laravel\Facades\Geocoder; + ``` -Support -------- +6. Update your query statements to use `->get()` (to retrieve a collection of + GeoCoder objects) or `->all()` (to retrieve an array of arrays), then iterate + to process each result. -[Please open an issue on GitHub](https://github.com/geocoder-php/GeocoderLaravel/issues) +## Troubleshooting +- Clear cache: `php artisan cache:clear`. +- If you are still experiencing difficulties, please please open an issue on GitHub: + https://github.com/geocoder-php/GeocoderLaravel/issues. +## Changelog +https://github.com/geocoder-php/GeocoderLaravel/blob/master/CHANGELOG.md -License -------- +## Contributor Code of Conduct +Please note that this project is released with a + [Contributor Code of Conduct](https://github.com/geocoder-php/Geocoder#contributor-code-of-conduct). + By participating in this project you agree to abide by its terms. +## License GeocoderLaravel is released under the MIT License. See the bundled -[LICENSE](https://github.com/geocoder-php/GeocoderLaravel/blob/master/LICENSE) -file for details. + [LICENSE](https://github.com/geocoder-php/GeocoderLaravel/blob/master/LICENSE) + file for details. diff --git a/composer.json b/composer.json index b022ccf..21cd476 100644 --- a/composer.json +++ b/composer.json @@ -1,37 +1,76 @@ { - "name" : "toin0u/geocoder-laravel", - "description" : "Geocoder Service provider for Laravel 4", - "keywords" : ["laravel", "geocoder", "geocoding"], - "homepage" : "http://geocoder-php.org/", - "license" : "MIT", - - "authors" : [{ - "name" : "Antoine Corcy", - "email" : "contact@sbin.dk", - "homepage" : "http://sbin.dk", - "role" : "Developer" - }], - - "require" : { - "php" : ">=5.3.0", - "illuminate/support" : "~4.0", - "willdurand/geocoder" : "~2.3" + "name": "toin0u/geocoder-laravel", + "description": "Geocoder Service Provider for Laravel", + "keywords": [ + "laravel", + "geocoder", + "geocoding" + ], + "homepage": "http://geocoder-php.org/", + "license": "MIT", + "authors": [ + { + "name": "Mike Bronner", + "email": "hello@genealabs.com", + "homepage": "https://genealabs.com", + "role": "Developer, Maintainer" + }, + { + "name": "Antoine Corcy", + "email": "contact@sbin.dk", + "homepage": "http://sbin.dk", + "role": "Original Creator" + } + ], + "require": { + "geocoder-php/chain-provider": "^4.0|^5.0", + "geocoder-php/geo-plugin-provider": "^4.0|^5.0", + "geocoder-php/google-maps-provider": "^4.0|^5.0", + "guzzlehttp/psr7": "*", + "http-interop/http-factory-guzzle": "^1.0", + "illuminate/cache": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php-http/curl-client": "*", + "php": "^8.0", + "willdurand/geocoder": "^4.0|^5.0" + }, + "require-dev": { + "doctrine/dbal": "*", + "geocoder-php/bing-maps-provider": "^4.0", + "geocoder-php/geoip2-provider": "^4.0", + "geocoder-php/maxmind-binary-provider": "^4.0", + "laravel/legacy-factories": "^1.0", + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", + "orchestra/testbench-browser-kit": "^7.0|^8.5|^10.0", + "orchestra/testbench-dusk": "^7.0|^8.22|^10.0", + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "8.5|^9.0|^10.5|^11.5.3" }, - - "require-dev" : { - "orchestra/testbench" : "~2.0", - "satooshi/php-coveralls" : "~0.6" + "autoload": { + "psr-4": { + "Geocoder\\Laravel\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\": "vendor/laravel/laravel/app/", + "Geocoder\\Laravel\\Tests\\": "tests/" + } }, - - "autoload" : { - "psr-0" : { - "Toin0u\\Geocoder" : "src/" + "extra": { + "laravel": { + "providers": [ + "Geocoder\\Laravel\\Providers\\GeocoderService" + ] } }, - - "extra" : { - "branch-alias" : { - "dev-master" : "0.2-dev" + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": true } } } diff --git a/config/geocoder.php b/config/geocoder.php new file mode 100644 index 0000000..c48c183 --- /dev/null +++ b/config/geocoder.php @@ -0,0 +1,110 @@ + [ + + /* + |----------------------------------------------------------------------- + | Cache Store + |----------------------------------------------------------------------- + | + | Specify the cache store to use for caching. The value "null" will use + | the default cache store specified in /config/cache.php file. + | + | Default: null + | + */ + + 'store' => null, + + /* + |----------------------------------------------------------------------- + | Cache Duration + |----------------------------------------------------------------------- + | + | Specify the cache duration in seconds. The default approximates a + | "forever" cache, but there are certain issues with Laravel's forever + | caching methods that prevent us from using them in this project. + | + | Default: 9999999 (integer) + | + */ + + 'duration' => 9999999, + ], + + /* + |--------------------------------------------------------------------------- + | Providers + |--------------------------------------------------------------------------- + | + | Here you may specify any number of providers that should be used to + | perform geocaching operations. The `chain` provider is special, + | in that it can contain multiple providers that will be run in + | the sequence listed, should the previous provider fail. By + | default the first provider listed will be used, but you + | can explicitly call subsequently listed providers by + | alias: `app('geocoder')->using('google_maps')`. + | + | Please consult the official Geocoder documentation for more info. + | https://github.com/geocoder-php/Geocoder#providers + | + */ + 'providers' => [ + Chain::class => [ + GoogleMaps::class => [ + env('GOOGLE_MAPS_LOCALE', 'us'), + env('GOOGLE_MAPS_API_KEY'), + ], + GeoPlugin::class => [], + ], + ], + + /* + |--------------------------------------------------------------------------- + | Adapter + |--------------------------------------------------------------------------- + | + | You can specify which PSR-7-compliant HTTP adapter you would like to use. + | There are multiple options at your disposal: CURL, Guzzle, and others. + | + | Please consult the official Geocoder documentation for more info. + | https://github.com/geocoder-php/Geocoder#usage + | + | Default: Client::class (FQCN for CURL adapter) + | + */ + 'adapter' => Client::class, + + /* + |--------------------------------------------------------------------------- + | Reader + |--------------------------------------------------------------------------- + | + | You can specify a reader for specific providers, like GeoIp2, which + | connect to a local file-database. The reader should be set to an + | instance of the required reader class or an array containing the reader + | class and arguments. + | + | Please consult the official Geocoder documentation for more info. + | https://github.com/geocoder-php/geoip2-provider + | + | Default: null + | + | Example: + | 'reader' => [ + | WebService::class => [ + | env('MAXMIND_USER_ID'), + | env('MAXMIND_LICENSE_KEY') + | ], + | ], + | + */ + 'reader' => null, + +]; diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..e69de29 diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..1524cdf --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,34 @@ + + + + + ./src + + + + + ./tests/Feature + + + + + + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index aef6a67..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - ./tests/ - - - - - ./src/Toin0u/ - - - diff --git a/src/Exceptions/InvalidDumperException.php b/src/Exceptions/InvalidDumperException.php new file mode 100644 index 0000000..e5a1a13 --- /dev/null +++ b/src/Exceptions/InvalidDumperException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Geocoder\Laravel\Exceptions; + +use Exception as BaseException; +use Geocoder\Exception\Exception; + +/** + * Exception to indicate an invalidly specified dumper identifier when calling + * the `dump()` method on the ProviderAndDumperAggregator class. + * + * @author Mike Bronner + */ +class InvalidDumperException extends BaseException implements Exception +{ + +} diff --git a/src/Toin0u/Geocoder/GeocoderFacade.php b/src/Facades/Geocoder.php similarity index 74% rename from src/Toin0u/Geocoder/GeocoderFacade.php rename to src/Facades/Geocoder.php index 7e76b12..04d576a 100644 --- a/src/Toin0u/Geocoder/GeocoderFacade.php +++ b/src/Facades/Geocoder.php @@ -1,4 +1,4 @@ - */ -class GeocoderFacade extends Facade +class Geocoder extends Facade { /** - * Get the registered name of the component. - * - * @return string - */ + * Get the registered name of the component. + * + * @return string + */ protected static function getFacadeAccessor() { return 'geocoder'; diff --git a/src/ProviderAndDumperAggregator.php b/src/ProviderAndDumperAggregator.php new file mode 100644 index 0000000..a5c07c9 --- /dev/null +++ b/src/ProviderAndDumperAggregator.php @@ -0,0 +1,337 @@ + + * @license MIT License + */ + +use Geocoder\Dumper\GeoJson; +use Geocoder\Dumper\Gpx; +use Geocoder\Dumper\Kml; +use Geocoder\Dumper\Wkb; +use Geocoder\Dumper\Wkt; +use Geocoder\Laravel\Exceptions\InvalidDumperException; +use Geocoder\ProviderAggregator; +use Geocoder\Query\GeocodeQuery; +use Geocoder\Query\ReverseQuery; +use Illuminate\Log\Logger; +use Illuminate\Support\Collection; +use Illuminate\Support\Str; +use Psr\Log\LoggerAwareTrait; +use ReflectionClass; + +/** + * @SuppressWarnings(PHPMD.TooManyPublicMethods) + */ +class ProviderAndDumperAggregator +{ + protected $aggregator; + protected $limit; + protected $results; + protected $isCaching = true; + + public function __construct() + { + $this->aggregator = new ProviderAggregator(); + $this->results = collect(); + } + + /** + * @deprecated Use `get()` instead. + */ + public function all() : array + { + return $this->results->all(); + } + + public function get() : Collection + { + return $this->results; + } + + public function toJson() : string + { + return $this + ->dump("geojson") + ->first(); + } + + public function doNotCache() : self + { + $this->isCaching = false; + + return $this; + } + + public function dump(string $dumper) : Collection + { + $dumperClasses = collect([ + 'geojson' => GeoJson::class, + 'gpx' => Gpx::class, + 'kml' => Kml::class, + 'wkb' => Wkb::class, + 'wkt' => Wkt::class, + ]); + + if (!$dumperClasses->has($dumper)) { + $errorMessage = implode('', [ + "The dumper specified ('{$dumper}') is invalid. Valid dumpers ", + "are: geojson, gpx, kml, wkb, wkt.", + ]); + + throw new InvalidDumperException($errorMessage); + } + + $dumperClass = $dumperClasses->get($dumper); + $dumper = new $dumperClass; + $results = collect($this->results->all()); + + return $results->map(function ($result) use ($dumper) { + return $dumper->dump($result); + }); + } + + public function geocode(string $value) : self + { + $cacheKey = (new Str)->slug(strtolower(urlencode($value))); + $this->results = $this->cacheRequest($cacheKey, [$value], "geocode"); + + return $this; + } + + public function geocodeQuery(GeocodeQuery $query) : self + { + $cacheKey = serialize($query); + $this->results = $this->cacheRequest($cacheKey, [$query], "geocodeQuery"); + + return $this; + } + + public function getLimit() : int + { + return $this->limit; + } + + public function getName() : string + { + return $this->aggregator->getName(); + } + + public function limit(int $limit) : self + { + $this->aggregator = new ProviderAggregator(null, $limit); + $this->registerProvidersFromConfig(collect(config('geocoder.providers'))); + $this->limit = $limit; + + return $this; + } + + public function getProvider() + { + $reflectedClass = new ReflectionClass(ProviderAggregator::class); + $reflectedProperty = $reflectedClass->getProperty('provider'); + $reflectedProperty->setAccessible(true); + + return $reflectedProperty->getValue($this->aggregator) + ?? $this->getProviders()->first(); + } + + public function getProviders() : Collection + { + return collect($this->aggregator->getProviders()); + } + + public function registerProvider($provider) : self + { + $this->aggregator->registerProvider($provider); + + return $this; + } + + public function registerProviders(array $providers = []) : self + { + $this->aggregator->registerProviders($providers); + + return $this; + } + + public function registerProvidersFromConfig(Collection $providers) : self + { + $this->registerProviders($this->getProvidersFromConfiguration($providers)); + + return $this; + } + + public function reverse(float $latitude, float $longitude) : self + { + $cacheKey = (new Str)->slug(strtolower(urlencode("{$latitude}-{$longitude}"))); + $this->results = $this->cacheRequest($cacheKey, [$latitude, $longitude], "reverse"); + + return $this; + } + + public function reverseQuery(ReverseQuery $query) : self + { + $cacheKey = serialize($query); + $this->results = $this->cacheRequest($cacheKey, [$query], "reverseQuery"); + + return $this; + } + + public function using(string $name) : self + { + $this->aggregator = $this->aggregator->using($name); + + return $this; + } + + protected function cacheRequest(string $cacheKey, array $queryElements, string $queryType) + { + if (! $this->isCaching) { + $this->isCaching = true; + + return collect($this->aggregator->{$queryType}(...$queryElements)); + } + + $hashedCacheKey = sha1($this->getProvider()->getName() . "-" . $cacheKey); + $duration = config("geocoder.cache.duration", 0); + $store = config('geocoder.cache.store'); + + $result = app("cache") + ->store($store) + ->remember($hashedCacheKey, $duration, function () use ($cacheKey, $queryElements, $queryType) { + return [ + "key" => $cacheKey, + "value" => collect($this->aggregator->{$queryType}(...$queryElements)), + ]; + }); + + $result = $this->preventCacheKeyHashCollision( + $result, + $hashedCacheKey, + $cacheKey, + $queryElements, + $queryType + ); + + $this->removeEmptyCacheEntry($result, $hashedCacheKey); + + return $result; + } + + protected function getAdapterClass(string $provider) : string + { + $specificAdapters = collect([ + 'Geocoder\Provider\GeoIP2\GeoIP2' => 'Geocoder\Provider\GeoIP2\GeoIP2Adapter', + 'Geocoder\Provider\MaxMindBinary\MaxMindBinary' => '', + ]); + + if ($specificAdapters->has($provider)) { + return $specificAdapters->get($provider); + } + + return config('geocoder.adapter'); + } + + protected function getReader() + { + $reader = config('geocoder.reader'); + + if (is_array(config('geocoder.reader'))) { + $readerClass = array_key_first(config('geocoder.reader')); + $readerArguments = config('geocoder.reader')[$readerClass]; + $reflection = new ReflectionClass($readerClass); + $reader = $reflection->newInstanceArgs($readerArguments); + } + + return $reader; + } + + protected function getArguments(array $arguments, string $provider) : array + { + if ($provider === 'Geocoder\Provider\Chain\Chain') { + return $this->getProvidersFromConfiguration( + collect(config('geocoder.providers.Geocoder\Provider\Chain\Chain')) + ); + } + + $adapter = $this->getAdapterClass($provider); + + if ($adapter) { + if ($this->requiresReader($provider)) { + $adapter = new $adapter($this->getReader()); + } else { + $adapter = new $adapter; + } + + array_unshift($arguments, $adapter); + } + + return $arguments; + } + + protected function getProvidersFromConfiguration(Collection $providers) : array + { + $providers = $providers->map(function ($arguments, $provider) { + $arguments = $this->getArguments($arguments, $provider); + $reflection = new ReflectionClass($provider); + + if ($provider === "Geocoder\Provider\Chain\Chain") { + $chainProvider = $reflection->newInstance($arguments); + + if (class_exists(Logger::class) + && in_array(LoggerAwareTrait::class, class_uses($chainProvider)) + && app(Logger::class) !== null + ) { + $chainProvider->setLogger(app(Logger::class)); + } + + return $chainProvider; + } + + return $reflection->newInstanceArgs($arguments); + }); + + return $providers->toArray(); + } + + protected function preventCacheKeyHashCollision( + array $result, + string $hashedCacheKey, + string $cacheKey, + array $queryElements, + string $queryType + ) { + if ($result["key"] === $cacheKey) { + return $result["value"]; + } + + app("cache") + ->store(config('geocoder.cache.store')) + ->forget($hashedCacheKey); + + return $this->cacheRequest($cacheKey, $queryElements, $queryType); + } + + protected function removeEmptyCacheEntry(Collection $result, string $cacheKey) + { + if ($result && $result->isEmpty()) { + app('cache') + ->store(config('geocoder.cache.store')) + ->forget($cacheKey); + } + } + + protected function requiresReader(string $class) : bool + { + $specificAdapters = collect([ + 'Geocoder\Provider\GeoIP2\GeoIP2', + ]); + + return $specificAdapters->contains($class); + } +} diff --git a/src/Providers/GeocoderService.php b/src/Providers/GeocoderService.php new file mode 100644 index 0000000..f815b9d --- /dev/null +++ b/src/Providers/GeocoderService.php @@ -0,0 +1,59 @@ + + * @license MIT License + */ + +use Geocoder\Laravel\Facades\Geocoder; +use Geocoder\Laravel\ProviderAndDumperAggregator; +use Illuminate\Support\ServiceProvider; + +class GeocoderService extends ServiceProvider +{ + protected $defer = false; + + public function boot() + { + $configPath = __DIR__ . "/../../config/geocoder.php"; + $this->publishes( + [$configPath => $this->configPath("geocoder.php")], + "config" + ); + $this->mergeConfigFrom($configPath, "geocoder"); + } + + public function register() + { + $this->app->alias("Geocoder", Geocoder::class); + $this->app->singleton(ProviderAndDumperAggregator::class, function () { + return (new ProviderAndDumperAggregator) + ->registerProvidersFromConfig(collect(config("geocoder.providers"))); + }); + $this->app->bind('geocoder', ProviderAndDumperAggregator::class); + } + + public function provides() : array + { + return ["geocoder", ProviderAndDumperAggregator::class]; + } + + protected function configPath(string $path = "") : string + { + if (function_exists("config_path")) { + return config_path($path); + } + + $pathParts = [ + app()->basePath(), + "config", + trim($path, "/"), + ]; + + return implode("/", $pathParts); + } +} diff --git a/src/Toin0u/Geocoder/GeocoderServiceProvider.php b/src/Toin0u/Geocoder/GeocoderServiceProvider.php deleted file mode 100644 index 36457a7..0000000 --- a/src/Toin0u/Geocoder/GeocoderServiceProvider.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Toin0u\Geocoder; - -use Geocoder\Geocoder; -use Illuminate\Support\ServiceProvider; - -/** - * Geocoder service provider - * - * @author Antoine Corcy - */ -class GeocoderServiceProvider extends ServiceProvider -{ - /** - * Indicates if loading of the provider is deferred. - * - * @var bool - */ - protected $defer = false; - - /** - * Bootstrap the application events. - * - * @return void - */ - public function boot() - { - $this->package('toin0u/geocoder-laravel'); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - $this->app->singleton('geocoder.adapter', function($app) { - $adapter = $app['config']->get('geocoder-laravel::adapter'); - - return new $adapter; - }); - - $this->app->singleton('geocoder.provider', function($app) { - $provider = $app['config']->get('geocoder-laravel::provider'); - - return new $provider($app['geocoder.adapter']); - }); - - $this->app['geocoder'] = $this->app->share(function($app) { - $geocoder = new Geocoder; - $geocoder->registerProvider($app['geocoder.provider']); - - return $geocoder; - }); - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { - return array('geocoder', 'geocoder.adapter', 'geocoder.provider'); - } -} diff --git a/src/config/config.php b/src/config/config.php deleted file mode 100644 index 98441ae..0000000 --- a/src/config/config.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -return array( - 'provider' => 'Geocoder\Provider\FreeGeoIpProvider', - 'adapter' => 'Geocoder\HttpAdapter\CurlHttpAdapter' -); diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php new file mode 100644 index 0000000..3abda79 --- /dev/null +++ b/tests/CreatesApplication.php @@ -0,0 +1,44 @@ +withFactories(__DIR__ . '/database/factories'); + $this->loadMigrationsFrom(__DIR__ . '/database/migrations'); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function getPackageProviders($app) + { + return [ + GeocoderService::class, + ]; + } + + protected function getEnvironmentSetUp($app) + { + $config = require(__DIR__ . '/config/testConfig.php'); + $app['config']->set('geocoder', $config); + $app['config']->set('database.redis.default', [ + 'host' => env('REDIS_HOST', '192.168.10.10'), + ]); + $app['config']->set('database.redis.geocode-cache', [ + 'host' => env('REDIS_HOST', '192.168.10.10'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => 1, + ]); + $app['config']->set('cache.stores.geocode', [ + 'driver' => 'redis', + 'connection' => 'geocode-cache', + ]); + $app['config']->set('geocoder.store', 'geocode'); + } +} diff --git a/tests/Feature/Providers/GeocoderServiceTest.php b/tests/Feature/Providers/GeocoderServiceTest.php new file mode 100644 index 0000000..5c42449 --- /dev/null +++ b/tests/Feature/Providers/GeocoderServiceTest.php @@ -0,0 +1,342 @@ +reverse(38.897957, -77.036560) + // ->get() + // ->filter(function (GoogleAddress $address) { + // return Str::contains($address->getStreetName() ?? '', 'Northwest'); + // }) + // ->first(); + + // $this->assertNotNull($result); + // $this->assertEquals('1600', $result->getStreetNumber()); + // $this->assertEquals('Pennsylvania Avenue Northwest', $result->getStreetName()); + // $this->assertEquals('Washington', $result->getLocality()); + // $this->assertEquals('20500', $result->getPostalCode()); + // } + + public function testItResolvesAGivenAddress() + { + $result = app('geocoder') + ->using('chain') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get() + ->first(); + + $this->assertNotNull($result); + $this->assertEquals('1600', $result->getStreetNumber()); + $this->assertEquals('Pennsylvania Avenue Northwest', $result->getStreetName()); + $this->assertEquals('Washington', $result->getLocality()); + $this->assertEquals('20500', $result->getPostalCode()); + } + + public function testItResolvesAGivenIPAddress() + { + $results = app('geocoder') + ->geocode('72.229.28.185') + ->get(); + + $this->assertTrue($results->isNotEmpty()); + $this->assertEquals('US', $results->first()->getCountry()->getCode()); + } + + public function testItResolvesAGivenAddressWithUmlauts() + { + $results = app('geocoder') + ->geocode('Obere Donaustrasse 22, Wien, Österreich') + ->get(); + + $this->assertEquals('22', $results->first()->getStreetNumber()); + $this->assertEquals('Obere Donaustraße', $results->first()->getStreetName()); + $this->assertEquals('Wien', $results->first()->getLocality()); + $this->assertEquals('1020', $results->first()->getPostalCode()); + $this->assertTrue($results->isNotEmpty()); + } + + public function testItResolvesAGivenAddressWithUmlautsInRegion() + { + $results = app('geocoder') + ->geocode('Obere Donaustraße 22, Wien, Österreich') + ->get(); + + $this->assertEquals('22', $results->first()->getStreetNumber()); + $this->assertEquals('Obere Donaustraße', $results->first()->getStreetName()); + $this->assertEquals('Wien', $results->first()->getLocality()); + $this->assertEquals('1020', $results->first()->getPostalCode()); + $this->assertTrue($results->isNotEmpty()); + } + + public function testItCanUseASpecificProvider() + { + $result = app('geocoder') + ->using('google_maps') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get() + ->first(); + + $this->assertNotNull($result); + $this->assertEquals('1600', $result->getStreetNumber()); + $this->assertEquals('Pennsylvania Avenue Northwest', $result->getStreetName()); + $this->assertEquals('Washington', $result->getLocality()); + $this->assertEquals('20500', $result->getPostalCode()); + } + + public function testItDumpsAndAddress() + { + $results = app('geocoder') + ->using('google_maps') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->dump('geojson'); + + $jsonAddress = json_decode($results->first()); + + $this->assertEquals('1600', $jsonAddress->properties->streetNumber); + $this->assertTrue($results->isNotEmpty()); + } + + public function testItThrowsAnExceptionForInvalidDumper() + { + $this->expectException(InvalidDumperException::class); + $results = app('geocoder') + ->using('google_maps') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->dump('test'); + $jsonAddress = json_decode($results->first()); + + $this->assertEquals('1600', $jsonAddress->properties->streetNumber); + $this->assertTrue($results->isNotEmpty()); + } + + public function testConfig() + { + $this->assertEquals(null, config('geocoder.cache.store')); + $this->assertEquals(999999999, config('geocoder.cache.duration')); + $this->assertTrue(is_array($providers = $this->app['config']->get('geocoder.providers'))); + $this->assertCount(3, $providers); + $this->assertArrayHasKey(GoogleMaps::class, $providers[Chain::class]); + $this->assertArrayHasKey(GeoPlugin::class, $providers[Chain::class]); + $this->assertSame(CurlAdapter::class, $this->app['config']->get('geocoder.adapter')); + } + + public function testLoadedProviders() + { + $loadedProviders = $this->app->getLoadedProviders(); + + $this->assertArrayHasKey(GeocoderService::class, $loadedProviders); + $this->assertTrue($loadedProviders[GeocoderService::class]); + } + + public function testGeocoder() + { + $this->assertInstanceOf(ProviderAndDumperAggregator::class, app('geocoder')); + } + + public function testCacheIsUsed() + { + $cacheKey = sha1( + app('geocoder')->getProvider()->getName() + . "-" . Str::slug(strtolower(urlencode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA'))) + ); + + $result = app('geocoder') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get(); + $cachedResult = app('cache') + ->store(config('geocoder.cache.store')) + ->get($cacheKey)["value"]; + $isCached = app('cache') + ->store(config('geocoder.cache.store')) + ->has($cacheKey); + + $this->assertTrue($isCached); + $this->assertEquals($result, $cachedResult); + } + + /** + * @SuppressWarnings(PHPMD.StaticAccess) + */ + public function testGeocodeQueryProvidesResults() + { + $query = GeocodeQuery::create('1600 Pennsylvania Ave NW, Washington, DC 20500, USA'); + + $results = app('geocoder')->geocodeQuery($query)->get(); + + $this->assertInstanceOf(Collection::class, $results); + $this->assertTrue($results->isNotEmpty()); + } + + /** + * @SuppressWarnings(PHPMD.StaticAccess) + */ + public function testReverseQueryProvidesResults() + { + $coordinates = new Coordinates(38.8791981, -76.9818437); + $query = ReverseQuery::create($coordinates); + + $results = app('geocoder')->reverseQuery($query)->get(); + + $this->assertInstanceOf(Collection::class, $results); + $this->assertTrue($results->isNotEmpty()); + } + + public function testFacadeProvidesResults() + { + $results = Geocoder::geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get(); + + $this->assertInstanceOf(Collection::class, $results); + $this->assertTrue($results->isNotEmpty()); + } + + public function testItCanUseMaxMindBinaryWithoutProvider() + { + $provider = new MaxMindBinary(__DIR__ . '/../../resources/assets/GeoIP.dat'); + app('geocoder')->registerProvider($provider); + + // dummy assertion, as we are running this test to make sure no + // exception is thrown in the above provider registration + $this->assertTrue(true); + } + + public function testGetNameReturnsString() + { + $this->assertEquals('provider_aggregator', app('geocoder')->getName()); + } + + public function testLimitingOfResults() + { + $expectedLimit = 1; + app('geocoder')->limit($expectedLimit); + $actualLimit = app('geocoder')->getLimit(); + $results = app('geocoder')->using('chain') + ->geocode('1600 Pennsylvania Ave., Washington, DC USA') + ->get(); + + $this->assertEquals($expectedLimit, $actualLimit); + $this->assertEquals($expectedLimit, $results->count()); + } + + public function testFetchingAllResults() + { + $expectedResults = app('geocoder') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get() + ->all(); + $actualResults = app('geocoder') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->all(); + + $this->assertEquals($expectedResults, $actualResults); + } + + public function testGetProviders() + { + $providers = app('geocoder')->getProviders(); + + $this->assertTrue($providers->has('chain')); + $this->assertTrue($providers->has('bing_maps')); + $this->assertTrue($providers->has('google_maps')); + } + + public function testGetProvider() + { + $provider = app('geocoder')->getProvider(); + + $this->assertEquals($provider->getName(), 'chain'); + } + + public function testJapaneseCharacterGeocoding() + { + $cacheKey = sha1(app('geocoder')->getProvider()->getName() + . "-" . Str::slug(strtolower(urlencode('108-0075 東京都港区港南2丁目16-3'))) + ); + + app('geocoder') + ->geocode('108-0075 東京都港区港南2丁目16-3') + ->get(); + + $this->assertTrue(app('cache')->has($cacheKey)); + } + + public function testItProvidesState() + { + $results = Geocoder::geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get(); + + $this->assertEquals('Washington', $results->first()->getLocality()); + } + + public function testItProvidesAdminLevel() + { + $results = Geocoder::geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') + ->get(); + + $this->assertEquals('District of Columbia', $results->first()->getAdminLevels()->first()->getName()); + } + + public function testItHandlesOnlyCityAndState() + { + $results = Geocoder::geocode('Seatle, WA')->get(); + + $this->assertEquals('Seattle', $results->first()->getLocality()); + $this->assertEquals('Washington', $results->first()->getAdminLevels()->first()->getName()); + $this->assertEquals('United States', $results->first()->getCountry()->getName()); + } + + public function testEmptyResultsAreNotCached() + { + $cacheKey = md5(Str::slug(strtolower(urlencode('_')))); + + Geocoder::geocode('_')->get(); + + $this->assertFalse(app('cache')->has("geocoder-{$cacheKey}")); + } + + public function testCachingCanBeDisabled() + { + $results = app("geocoder") + ->doNotCache() + ->geocode('Los Angeles, CA') + ->get(); + + $this->assertEquals( + "Los Angeles, CA, USA", + $results->first()->getFormattedAddress() + ); + } + + public function testGetProviderReturnsCurrentProvider() + { + $provider = app("geocoder") + ->using("google_maps") + ->getProvider(); + + $this->assertEquals("google_maps", $provider->getName()); + } +} diff --git a/tests/FeatureTestCase.php b/tests/FeatureTestCase.php new file mode 100644 index 0000000..3816dbb --- /dev/null +++ b/tests/FeatureTestCase.php @@ -0,0 +1,8 @@ + - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Geocoder\Tests; - -/** - * @author Antoine Corcy - */ -class GeocoderFacadeTest extends TestCase -{ - public function testGeocoderFacade() - { - $this->assertTrue(is_array($providers = \Geocoder::getProviders())); - $this->assertArrayHasKey('free_geo_ip', $providers); - $this->assertInstanceOf('Geocoder\\Provider\\FreeGeoipProvider', $providers['free_geo_ip']); - } -} diff --git a/tests/Geocoder/Tests/GeocoderServiceProviderTest.php b/tests/Geocoder/Tests/GeocoderServiceProviderTest.php deleted file mode 100644 index b7cde81..0000000 --- a/tests/Geocoder/Tests/GeocoderServiceProviderTest.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Geocoder\Tests; - -/** - * @author Antoine Corcy - */ -class GeocoderServiceProviderTest extends TestCase -{ - public function testConfig() - { - $this->assertSame('Geocoder\Provider\FreeGeoIpProvider', $this->app['config']->get('geocoder-laravel::provider')); - $this->assertSame('Geocoder\HttpAdapter\CurlHttpAdapter', $this->app['config']->get('geocoder-laravel::adapter')); - } - - public function testLoadedProviders() - { - $loadedProviders = $this->app->getLoadedProviders(); - - $this->assertArrayHasKey('Toin0u\\Geocoder\\GeocoderServiceProvider', $loadedProviders); - $this->assertTrue($loadedProviders['Toin0u\\Geocoder\\GeocoderServiceProvider']); - } - - public function testGeocoderDefaultAdapter() - { - $this->assertInstanceOf('Geocoder\\HttpAdapter\\CurlHttpAdapter', $this->app['geocoder.adapter']); - } - - public function testGeocoderDefaultProvider() - { - $this->assertInstanceOf('Geocoder\\Provider\\FreeGeoIpProvider', $this->app['geocoder.provider']); - } - - public function testGeocoder() - { - $this->assertInstanceOf('Geocoder\\Geocoder', $this->app['geocoder']); - } -} diff --git a/tests/Geocoder/Tests/TestCase.php b/tests/Geocoder/Tests/TestCase.php deleted file mode 100644 index cb1fbab..0000000 --- a/tests/Geocoder/Tests/TestCase.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Geocoder\Tests; - -/** - * @author Antoine Corcy - */ -class TestCase extends \Orchestra\Testbench\TestCase -{ - protected function getPackageProviders() - { - return array( - 'Toin0u\Geocoder\GeocoderServiceProvider', - ); - } - protected function getPackageAliases() - { - return array( - 'Geocoder' => 'Toin0u\Geocoder\GeocoderFacade', - ); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 421e630..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -add('Geocoder\Tests', __DIR__); diff --git a/tests/config/testConfig.php b/tests/config/testConfig.php new file mode 100644 index 0000000..9ee6af6 --- /dev/null +++ b/tests/config/testConfig.php @@ -0,0 +1,47 @@ + [ + 'store' => null, + 'duration' => 999999999, + ], + 'providers' => [ + Chain::class => [ + GeoIP2::class => [], + GoogleMaps::class => [ + 'en-US', + env('GOOGLE_MAPS_API_KEY'), + ], + GeoPlugin::class => [], + ], + BingMaps::class => [ + 'en-US', + env('BING_MAPS_API_KEY'), + ], + GoogleMaps::class => [ + 'us', + env('GOOGLE_MAPS_API_KEY'), + ], + ], + 'adapter' => Client::class, + // 'reader' => new Reader(__DIR__ . '/../resources/assets/GeoLite2-City.mmdb'), + "reader" => [ + Reader::class => [ + __DIR__ . '/../resources/assets/GeoLite2-City.mmdb', + ], + ], +]; diff --git a/tests/resources/assets/GeoIP.dat b/tests/resources/assets/GeoIP.dat new file mode 100644 index 0000000..f2945fd Binary files /dev/null and b/tests/resources/assets/GeoIP.dat differ diff --git a/tests/resources/assets/GeoLite2-City.mmdb b/tests/resources/assets/GeoLite2-City.mmdb new file mode 100644 index 0000000..835e93d Binary files /dev/null and b/tests/resources/assets/GeoLite2-City.mmdb differ