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 90c60f2..a586f6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: php php: - - 7.0 - - 7.1 - - 7.2 + - 7.3 + - 7.4 before_script: - travis_retry composer self-update diff --git a/CHANGELOG.md b/CHANGELOG.md index 6722c26..bf9a76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,54 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.3.4] - 2020-06-21 +### Fixed +- non-caching declaration to only apply to current query. +- caching to take provider into account. + +### 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()`. + +## [4.3.0] - 2020-02-29 +### Added +- Laravel 7 compatibility. + +## [4.1.2] - 23 May 2019 +### Fixed +- initialization of geocoder adapter. + +## [4.0.21] - 3 Nov 2018 +### Added +- `->toJson()` method when querying results. + +## [4.0.10] - 1 Jul 2018 +### Changed +- service provider to register singleton and alias in `register()` method. + +## [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. diff --git a/README.md b/README.md index 71cce92..268684c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This package allows you to use [**Geocoder**](http://geocoder-php.org/Geocoder/) in [**Laravel 5**](http://laravel.com/). ## Requirements -- PHP >= 7.0.0 +- PHP >= 7.1.3 - Laravel >= 5.0 ## Installation @@ -56,11 +56,59 @@ Further, a special note on the GoogleMaps provider: if you are using an API key, 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 + ], + ] +``` + +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", + + // ... + ], +``` + +#### 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` minutes, essentially forever. + 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, @@ -113,7 +161,7 @@ return [ 'providers' => [ Chain::class => [ GoogleMaps::class => [ - env('GOOGLE_MAPS_LOCALE', 'en-US'), + env('GOOGLE_MAPS_LOCALE', 'us'), env('GOOGLE_MAPS_API_KEY'), ], GeoPlugin::class => [], @@ -143,7 +191,8 @@ return [ | | 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. + | 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 @@ -195,6 +244,19 @@ app('geocoder')->reverse(43.882587,-103.454067)->get(); app('geocoder')->geocode('Los Angeles, CA')->dump('kml'); ``` +#### Dependency Injection +```php +use Geocoder\Laravel\ProviderAndDumperAggregator as Geocoder; + +class GeocoderController extends Controller +{ + public function getGeocode(Geocoder $geocoder) + { + $geocoder->geocode('Los Angeles, CA')->get() + } +} +``` + ## 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 @@ -212,7 +274,7 @@ The one change to keep in mind here is that the results returned from 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 strait over to Laravel's `Collection` methods. But be sure + 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. diff --git a/composer.json b/composer.json index 8b6db23..21cd476 100644 --- a/composer.json +++ b/composer.json @@ -23,31 +23,28 @@ } ], "require": { - "geocoder-php/chain-provider": "^4.0", - "geocoder-php/geo-plugin-provider": "^4.0", - "geocoder-php/google-maps-provider": "^4.0", + "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": "*", - "illuminate/cache": "5.0 - 5.6", - "illuminate/support": "5.0 - 5.6", - "php-http/curl-client": "^1.7", - "php": "^7.0", - "willdurand/geocoder": "^4.0" + "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": { - "codedungeon/phpunit-result-printer": "*", - "doctrine/dbal": "^2.5", - "fzaninotto/faker": "~1.4", + "doctrine/dbal": "*", "geocoder-php/bing-maps-provider": "^4.0", "geocoder-php/geoip2-provider": "^4.0", "geocoder-php/maxmind-binary-provider": "^4.0", - "mockery/mockery": "0.9.*", - "orchestra/database": "3.6.x-dev@dev", - "orchestra/testbench": "^3.6.0", - "orchestra/testbench-browser-kit": "3.6.x-dev@dev", - "orchestra/testbench-dusk": "3.6.x-dev@dev", + "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": "^7.0", - "sebastian/phpcpd": "*" + "phpunit/phpunit": "8.5|^9.0|^10.5|^11.5.3" }, "autoload": { "psr-4": { @@ -67,9 +64,13 @@ ] } }, + "minimum-stability": "dev", "prefer-stable": true, "config": { "preferred-install": "dist", - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/config/geocoder.php b/config/geocoder.php index 96e3c5c..c48c183 100644 --- a/config/geocoder.php +++ b/config/geocoder.php @@ -6,25 +6,42 @@ use Http\Client\Curl\Client; return [ + 'cache' => [ - /* - |-------------------------------------------------------------------------- - | Cache Duration - |-------------------------------------------------------------------------- - | - | Specify the cache duration in minutes. 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, + /* + |----------------------------------------------------------------------- + | 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, @@ -41,7 +58,7 @@ 'providers' => [ Chain::class => [ GoogleMaps::class => [ - env('GOOGLE_MAPS_LOCALE', 'en-US'), + env('GOOGLE_MAPS_LOCALE', 'us'), env('GOOGLE_MAPS_API_KEY'), ], GeoPlugin::class => [], @@ -49,9 +66,9 @@ ], /* - |-------------------------------------------------------------------------- + |--------------------------------------------------------------------------- | 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. @@ -65,19 +82,28 @@ '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. + | 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 index f315fa4..1524cdf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,43 +1,34 @@ - - - ./tests/Browser - - + + + ./src + + + - ./tests/Feature - - - - ./tests/Unit + ./tests/Feature - - - - ./src - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/ProviderAndDumperAggregator.php b/src/ProviderAndDumperAggregator.php index 5b25b64..a5c07c9 100644 --- a/src/ProviderAndDumperAggregator.php +++ b/src/ProviderAndDumperAggregator.php @@ -14,12 +14,14 @@ use Geocoder\Dumper\Kml; use Geocoder\Dumper\Wkb; use Geocoder\Dumper\Wkt; -use Geocoder\Geocoder; -use Geocoder\Query\GeocodeQuery; -use Geocoder\Query\ReverseQuery; 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; /** @@ -30,6 +32,7 @@ class ProviderAndDumperAggregator protected $aggregator; protected $limit; protected $results; + protected $isCaching = true; public function __construct() { @@ -50,6 +53,20 @@ 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([ @@ -65,6 +82,7 @@ public function dump(string $dumper) : Collection "The dumper specified ('{$dumper}') is invalid. Valid dumpers ", "are: geojson, gpx, kml, wkb, wkt.", ]); + throw new InvalidDumperException($errorMessage); } @@ -77,73 +95,30 @@ public function dump(string $dumper) : Collection }); } - public function geocodeQuery(GeocodeQuery $query) : self + public function geocode(string $value) : self { - $cacheKey = serialize($query); - $this->results = app('cache')->remember( - "geocoder-{$cacheKey}", - config('geocoder.cache-duration', 0), - function () use ($query) { - return collect($this->aggregator->geocodeQuery($query)); - } - ); - - $this->removeEmptyCacheEntry("geocoder-{$cacheKey}"); + $cacheKey = (new Str)->slug(strtolower(urlencode($value))); + $this->results = $this->cacheRequest($cacheKey, [$value], "geocode"); return $this; } - public function reverseQuery(ReverseQuery $query) : self + public function geocodeQuery(GeocodeQuery $query) : self { $cacheKey = serialize($query); - $this->results = app('cache')->remember( - "geocoder-{$cacheKey}", - config('geocoder.cache-duration', 0), - function () use ($query) { - return collect($this->aggregator->reverseQuery($query)); - } - ); - - $this->removeEmptyCacheEntry("geocoder-{$cacheKey}"); + $this->results = $this->cacheRequest($cacheKey, [$query], "geocodeQuery"); return $this; } - public function getName() : string - { - return $this->aggregator->getName(); - } - - public function geocode(string $value) : self + public function getLimit() : int { - $cacheKey = str_slug(strtolower(urlencode($value))); - $this->results = app('cache')->remember( - "geocoder-{$cacheKey}", - config('geocoder.cache-duration', 0), - function () use ($value) { - return collect($this->aggregator->geocode($value)); - } - ); - - $this->removeEmptyCacheEntry("geocoder-{$cacheKey}"); - - return $this; + return $this->limit; } - public function reverse(float $latitude, float $longitude) : self + public function getName() : string { - $cacheKey = str_slug(strtolower(urlencode("{$latitude}-{$longitude}"))); - $this->results = app('cache')->remember( - "geocoder-{$cacheKey}", - config('geocoder.cache-duration', 0), - function () use ($latitude, $longitude) { - return collect($this->aggregator->reverse($latitude, $longitude)); - } - ); - - $this->removeEmptyCacheEntry("geocoder-{$cacheKey}"); - - return $this; + return $this->aggregator->getName(); } public function limit(int $limit) : self @@ -155,9 +130,19 @@ public function limit(int $limit) : self return $this; } - public function getLimit() : int + public function getProvider() { - return $this->limit; + $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 @@ -174,47 +159,96 @@ public function registerProviders(array $providers = []) : self return $this; } - public function using(string $name) : self + public function registerProvidersFromConfig(Collection $providers) : self { - $this->aggregator = $this->aggregator->using($name); + $this->registerProviders($this->getProvidersFromConfiguration($providers)); return $this; } - public function getProviders() : Collection + public function reverse(float $latitude, float $longitude) : self { - return collect($this->aggregator->getProviders()); + $cacheKey = (new Str)->slug(strtolower(urlencode("{$latitude}-{$longitude}"))); + $this->results = $this->cacheRequest($cacheKey, [$latitude, $longitude], "reverse"); + + return $this; } - /** - * @deprecated Use `getProviders()` instead. - */ - public function getProvider() + public function reverseQuery(ReverseQuery $query) : self { - return $this->getProviders()->first(); + $cacheKey = serialize($query); + $this->results = $this->cacheRequest($cacheKey, [$query], "reverseQuery"); + + return $this; } - public function registerProvidersFromConfig(Collection $providers) : self + public function using(string $name) : self { - $this->registerProviders($this->getProvidersFromConfiguration($providers)); + $this->aggregator = $this->aggregator->using($name); return $this; } - protected function getProvidersFromConfiguration(Collection $providers) : array + protected function cacheRequest(string $cacheKey, array $queryElements, string $queryType) { - $providers = $providers->map(function ($arguments, $provider) { - $arguments = $this->getArguments($arguments, $provider); - $reflection = new ReflectionClass($provider); + if (! $this->isCaching) { + $this->isCaching = true; - if ($provider === 'Geocoder\Provider\Chain\Chain') { - return $reflection->newInstance($arguments); - } + return collect($this->aggregator->{$queryType}(...$queryElements)); + } - return $reflection->newInstanceArgs($arguments); - }); + $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 + ); - return $providers->toArray(); + $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 @@ -226,48 +260,78 @@ protected function getArguments(array $arguments, string $provider) : array } $adapter = $this->getAdapterClass($provider); - $reader = null; if ($adapter) { if ($this->requiresReader($provider)) { - $reader = config('geocoder.reader'); + $adapter = new $adapter($this->getReader()); + } else { + $adapter = new $adapter; } - array_unshift($arguments, new $adapter($reader)); + array_unshift($arguments, $adapter); } return $arguments; } - protected function requiresReader(string $class) : bool + protected function getProvidersFromConfiguration(Collection $providers) : array { - $specificAdapters = collect([ - 'Geocoder\Provider\GeoIP2\GeoIP2', - ]); + $providers = $providers->map(function ($arguments, $provider) { + $arguments = $this->getArguments($arguments, $provider); + $reflection = new ReflectionClass($provider); - return $specificAdapters->contains($class); - } + if ($provider === "Geocoder\Provider\Chain\Chain") { + $chainProvider = $reflection->newInstance($arguments); - protected function getAdapterClass(string $provider) : string - { - $specificAdapters = collect([ - 'Geocoder\Provider\GeoIP2\GeoIP2' => 'Geocoder\Provider\GeoIP2\GeoIP2Adapter', - 'Geocoder\Provider\MaxMindBinary\MaxMindBinary' => '', - ]); + if (class_exists(Logger::class) + && in_array(LoggerAwareTrait::class, class_uses($chainProvider)) + && app(Logger::class) !== null + ) { + $chainProvider->setLogger(app(Logger::class)); + } - if ($specificAdapters->has($provider)) { - return $specificAdapters->get($provider); + 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"]; } - return config('geocoder.adapter'); + app("cache") + ->store(config('geocoder.cache.store')) + ->forget($hashedCacheKey); + + return $this->cacheRequest($cacheKey, $queryElements, $queryType); } - protected function removeEmptyCacheEntry(string $cacheKey) + protected function removeEmptyCacheEntry(Collection $result, string $cacheKey) { - $result = app('cache')->get($cacheKey); - if ($result && $result->isEmpty()) { - app('cache')->forget($cacheKey); + 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 index 97d4aa2..f815b9d 100644 --- a/src/Providers/GeocoderService.php +++ b/src/Providers/GeocoderService.php @@ -11,9 +11,7 @@ use Geocoder\Laravel\Facades\Geocoder; use Geocoder\Laravel\ProviderAndDumperAggregator; -use Illuminate\Support\Collection; use Illuminate\Support\ServiceProvider; -use ReflectionClass; class GeocoderService extends ServiceProvider { @@ -21,22 +19,41 @@ class GeocoderService extends ServiceProvider public function boot() { - $configPath = __DIR__ . '/../../config/geocoder.php'; - $this->publishes([$configPath => config_path('geocoder.php')], 'config'); - $this->mergeConfigFrom($configPath, 'geocoder'); - $this->app->singleton('geocoder', function () { - return (new ProviderAndDumperAggregator) - ->registerProvidersFromConfig(collect(config('geocoder.providers'))); - }); + $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->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']; + 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/tests/BrowserTestCase.php b/tests/BrowserTestCase.php deleted file mode 100644 index 8b191b6..0000000 --- a/tests/BrowserTestCase.php +++ /dev/null @@ -1,18 +0,0 @@ -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 index 93ef198..5c42449 100644 --- a/tests/Feature/Providers/GeocoderServiceTest.php +++ b/tests/Feature/Providers/GeocoderServiceTest.php @@ -1,21 +1,21 @@ 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 testItReverseGeocodesCoordinates() + // { + // $result = app('geocoder') + // ->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() { @@ -81,14 +81,8 @@ public function testItResolvesAGivenAddressWithUmlauts() public function testItResolvesAGivenAddressWithUmlautsInRegion() { - config()->set('geocoder.providers.Geocoder\Provider\Chain\Chain.Geocoder\Provider\GoogleMaps\GoogleMaps', [ - 'de-DE', - null, - ]); - app()->register(GeocoderService::class); - $results = app('geocoder') - ->geocode('Obere Donaustrasse 22, Wien, Österreich') + ->geocode('Obere Donaustraße 22, Wien, Österreich') ->get(); $this->assertEquals('22', $results->first()->getStreetNumber()); @@ -141,7 +135,8 @@ public function testItThrowsAnExceptionForInvalidDumper() public function testConfig() { - $this->assertEquals(999999999, config('geocoder.cache-duration')); + $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]); @@ -164,13 +159,23 @@ public function testGeocoder() public function testCacheIsUsed() { - $cacheKey = str_slug(strtolower(urlencode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA'))); + $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') + $result = app('geocoder') + ->geocode('1600 Pennsylvania Ave NW, Washington, DC 20500, USA') ->get(); - - $this->assertTrue(app('cache')->has("geocoder-{$cacheKey}")); - $this->assertEquals($result, app('cache')->get("geocoder-{$cacheKey}")); + $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); } /** @@ -213,6 +218,10 @@ 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() @@ -264,16 +273,15 @@ public function testGetProvider() public function testJapaneseCharacterGeocoding() { - $cacheKey = str_slug(strtolower(urlencode('108-0075 東京都港区港南2丁目16-3'))); + $cacheKey = sha1(app('geocoder')->getProvider()->getName() + . "-" . Str::slug(strtolower(urlencode('108-0075 東京都港区港南2丁目16-3'))) + ); - app('geocoder')->geocode('108-0075 東京都港区港南2丁目16-3') + app('geocoder') + ->geocode('108-0075 東京都港区港南2丁目16-3') ->get(); - $this->assertEquals( - $cacheKey, - '108-0075e69db1e4baace983bde6b8afe58cbae6b8afe58d97efbc92e4b881e79baeefbc91efbc96efbc8defbc93' - ); - $this->assertTrue(app('cache')->has("geocoder-{$cacheKey}")); + $this->assertTrue(app('cache')->has($cacheKey)); } public function testItProvidesState() @@ -303,10 +311,32 @@ public function testItHandlesOnlyCityAndState() public function testEmptyResultsAreNotCached() { - $cacheKey = str_slug(strtolower(urlencode('_'))); + $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/UnitTestCase.php b/tests/UnitTestCase.php deleted file mode 100644 index 12c041f..0000000 --- a/tests/UnitTestCase.php +++ /dev/null @@ -1,8 +0,0 @@ - 999999999, + 'cache' => [ + 'store' => null, + 'duration' => 999999999, + ], 'providers' => [ Chain::class => [ GeoIP2::class => [], @@ -36,5 +38,10 @@ ], ], 'adapter' => Client::class, - 'reader' => new Reader(__DIR__ . '/../resources/assets/GeoLite2-City.mmdb'), + // 'reader' => new Reader(__DIR__ . '/../resources/assets/GeoLite2-City.mmdb'), + "reader" => [ + Reader::class => [ + __DIR__ . '/../resources/assets/GeoLite2-City.mmdb', + ], + ], ];