From 67e6d4a117e305b39062f4107850b95afe7643f0 Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Sun, 28 Oct 2018 15:10:53 -0400 Subject: [PATCH 1/8] Prepare 2.0 --- composer.json | 4 +- src/Exception.php | 6 +- src/Lookup.php | 30 ++++++--- src/LookupModel.php | 108 +++++++++--------------------- tests/IntegrationTest.php | 36 ++++++++++ tests/LookupModelTest.php | 7 +- tests/LookupTest.php | 10 +-- tests/Service/CurlServiceTest.php | 19 +++--- tests/bootstrap.php | 0 9 files changed, 117 insertions(+), 103 deletions(-) create mode 100644 tests/IntegrationTest.php delete mode 100644 tests/bootstrap.php diff --git a/composer.json b/composer.json index d3b9d31..fdd6923 100644 --- a/composer.json +++ b/composer.json @@ -2,10 +2,10 @@ "name": "ziptastic/ziptastic", "description": "PHP SDK for the Ziptastic Lookup API", "require": { - "php": ">=5.4" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "~4.0", + "phpunit/phpunit": "^6.0", "codeclimate/php-test-reporter": "dev-master" }, "license": "MIT", diff --git a/src/Exception.php b/src/Exception.php index 1a502ab..8d06dda 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -1,4 +1,6 @@ -service = $service; $this->apiKey = $apiKey; $this->countryCode = $countryCode; } - public static function create($apiKey = null, $countryCode = 'US') + /** + * Create a client instance with the default service. + * + * @param ?string $apiKey + * @param ?string $countryCode + * @return Lookup + */ + public static function create(string $apiKey = null, string $countryCode = 'US'): self { $service = new CurlService; return new self($service, $apiKey, $countryCode); @@ -43,9 +55,9 @@ public static function create($apiKey = null, $countryCode = 'US') /** * Get information on given $zipCode * @param string $zipCode - * @return array[LookupModel] + * @return LookupModel[] */ - public function lookup($zipCode) + public function lookup($zipCode): array { $url = sprintf(self::ZIPTASTIC_LOOKUP_URL, $this->countryCode, (string) $zipCode); $res = $this->service->get($url, $this->apiKey); diff --git a/src/LookupModel.php b/src/LookupModel.php index e0ba298..6e51cc8 100644 --- a/src/LookupModel.php +++ b/src/LookupModel.php @@ -1,133 +1,91 @@ -county = $this->getOrNull('county', $lookup); - $this->city = $this->getOrNull('city', $lookup); - $this->state = $this->getOrNull('state', $lookup); - $this->stateShort = $this->getOrNull('state_short', $lookup); - $this->postalCode = $this->getOrNull('postal_code', $lookup); - $this->latitude = $this->getOrNull('latitude', $lookup); - $this->longitude = $this->getOrNull('longitude', $lookup); - $timezone = $this->getOrNull('timezone', $lookup); - if (!is_null($timezone)) { - $this->timezone = new DateTimeZone($timezone); + $this->data = $data + [ + 'county' => null, + 'city' => null, + 'state' => null, + 'state_short' => null, + 'postal_code' => null, + 'latitude' => null, + 'longitude' => null, + 'timezone' => null, + ]; + + if ($this->data['timezone']) { + $this->data['timezone'] = new \DateTimeZone($this->data['timezone']); } } /** * @return string */ - public function county() + public function county(): ?string { - return $this->county; + return $this->data['county']; } /** * @return string */ - public function city() + public function city(): ?string { - return $this->city; + return $this->data['city']; } /** * @return string */ - public function state() + public function state(): ?string { - return $this->state; + return $this->data['state']; } /** * @return string */ - public function stateShort() + public function stateShort(): ?string { - return $this->stateShort; + return $this->data['state_short']; } /** * @return string */ - public function postalCode() + public function postalCode(): ?string { - return $this->postalCode; + return $this->data['postal_code']; } /** * @return float */ - public function latitude() + public function latitude(): ?float { - return $this->latitude; + return $this->data['latitude']; } /** * @return float */ - public function longitude() + public function longitude(): ?float { - return $this->longitude; + return $this->data['longitude']; } /** - * @return DateTimeZone + * @return \DateTimeZone */ - public function timezone() - { - return $this->timezone; - } - - private function getOrNull($key, array $data) + public function timezone(): ?\DateTimeZone { - return (isset($data[$key])) ? $data[$key] : null; + return $this->data['timezone']; } } diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php new file mode 100644 index 0000000..01a31d4 --- /dev/null +++ b/tests/IntegrationTest.php @@ -0,0 +1,36 @@ +apiKey = getenv('ZIPTASTIC_API_KEY'); + + if (!$this->apiKey) { + $this->markTestSkipped('No API Key.'); + } + } + + public function testZiptastic() + { + $lookup = Lookup::create($this->apiKey); + $l = $lookup->lookup(48038); + + foreach ($l as $model) { + $this->assertInternalType('string', $model->county()); + $this->assertInternalType('string', $model->city()); + $this->assertInternalType('string', $model->state()); + $this->assertInternalType('string', $model->stateShort()); + $this->assertInternalType('string', $model->postalCode()); + $this->assertInternalType('double', $model->latitude()); + $this->assertInternalType('double', $model->longitude()); + + $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); + } + } +} diff --git a/tests/LookupModelTest.php b/tests/LookupModelTest.php index 7155478..8295b02 100644 --- a/tests/LookupModelTest.php +++ b/tests/LookupModelTest.php @@ -1,8 +1,9 @@ assertEquals($model->latitude(), $data['latitude']); $this->assertEquals($model->longitude(), $data['longitude']); - $this->assertInstanceOf('DateTimeZone', $model->timezone()); + $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); $this->assertEquals($model->timezone()->getName(), 'America/Detroit'); } @@ -43,4 +44,4 @@ public function testNullConstructor() $this->assertNull($model->longitude()); $this->assertNull($model->timezone()); } -} \ No newline at end of file +} diff --git a/tests/LookupTest.php b/tests/LookupTest.php index d16ed46..67d17fb 100644 --- a/tests/LookupTest.php +++ b/tests/LookupTest.php @@ -1,8 +1,10 @@ 'Macomb', @@ -49,11 +51,11 @@ public function testCollection() public function testStatic() { $lookup = Lookup::create('123'); - $this->assertInstanceOf('Ziptastic\\Ziptastic\\Lookup', $lookup); + $this->assertInstanceOf(Lookup::class, $lookup); } } -class servicestub implements Ziptastic\Ziptastic\Service\ServiceInterface +class servicestub implements ServiceInterface { private $res; @@ -66,4 +68,4 @@ public function get($url, $headers) { return call_user_func($this->res); } -} \ No newline at end of file +} diff --git a/tests/Service/CurlServiceTest.php b/tests/Service/CurlServiceTest.php index f574181..c43ea5f 100644 --- a/tests/Service/CurlServiceTest.php +++ b/tests/Service/CurlServiceTest.php @@ -1,6 +1,9 @@ setExpectedException( - 'Ziptastic\\Ziptastic\\Exception', + $this->expectException( + Exception::class, 'Could not parse response as json' ); @@ -34,8 +37,8 @@ public function testGetMalformed() public function testGetInvalidStatusWithMessage() { - $this->setExpectedException( - 'Ziptastic\\Ziptastic\\Exception', + $this->expectException( + Exception::class, 'bad' ); @@ -48,8 +51,8 @@ public function testGetInvalidStatusWithMessage() public function testGetInvalidStatusWithoutMessage() { - $this->setExpectedException( - 'Ziptastic\\Ziptastic\\Exception', + $this->expectException( + Exception::class, 'An error occurred' ); @@ -98,4 +101,4 @@ protected function curl_close($ch) { return; } -} \ No newline at end of file +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index e69de29..0000000 From d4e592aeeff938d67244a1532f059b547b645f52 Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Sun, 28 Oct 2018 15:14:37 -0400 Subject: [PATCH 2/8] Update travis matrix --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0628c5f..d839d50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,7 @@ language: php php: - - '5.4' - - '5.5' - - '5.6' - - '7.0' + - 7.1 + - 7.2 install: - mkdir -p build/logs before_script: From b17797679cbccad35559a833be13ebc37ce67ccd Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Sun, 28 Oct 2018 20:45:22 -0400 Subject: [PATCH 3/8] Refactor --- LICENSE.md | 2 +- README.md | 37 +++--- composer.json | 2 +- demo.php | 7 -- src/Client.php | 113 ++++++++++++++++++ src/Exception.php | 2 +- src/Lookup.php | 72 ----------- src/{LookupModel.php => ResponseItem.php} | 7 +- src/Service/CurlService.php | 9 +- src/Service/ServiceInterface.php | 4 +- tests/{LookupTest.php => ClientTest.php} | 35 ++++-- tests/IntegrationTest.php | 28 ++++- ...okupModelTest.php => ResponseItemTest.php} | 8 +- tests/Service/CurlServiceTest.php | 4 +- 14 files changed, 206 insertions(+), 124 deletions(-) delete mode 100644 demo.php create mode 100644 src/Client.php delete mode 100644 src/Lookup.php rename src/{LookupModel.php => ResponseItem.php} (94%) rename tests/{LookupTest.php => ClientTest.php} (61%) rename tests/{LookupModelTest.php => ResponseItemTest.php} (90%) diff --git a/LICENSE.md b/LICENSE.md index b74ab80..3b0bc3b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,5 @@ The MIT License (MIT) -Copyright (c) 2015 John Pedrie +Copyright (c) 2018 Ziptastic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index a03a374..89418ff 100644 --- a/README.md +++ b/README.md @@ -18,36 +18,41 @@ composer require ziptastic/ziptastic The simplest usage defaults to the provided Curl service: -````php +```php lookup(48038); - -?> -```` +$z = Client::create(getenv('ZIPTASTIC_API_KEY')); +``` You can also use a normal instantiation technique: -````php +```php lookup(48038); +$service = new Ziptastic\Service\CurlService; +$z = new Client($service, getenv('ZIPTASTIC_API_KEY')); +``` -?> -```` +Ziptastic provides two API methods: Lookup by a postal code (forward lookup), +and lookup by latitude and longitude (reverse lookup). + +```php +$result = $z->forward(48038); +$result = $z->reverse(42.331427, -83.0457538, 1000); +``` Results are returned as a collection of class LookupModel: -````php +```php longitude(); // -82.9195514 // timezone() returns an instance of \DateTimeZone echo $lookup->timezone()->getName(); // America/Detroit +``` -?> -```` +### PHP 5 + +If you require PHP 5 compatibility, please use Ziptastic-PHP version 1. ## License diff --git a/composer.json b/composer.json index fdd6923..09d714c 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "autoload": { "psr-4": { - "Ziptastic\\Ziptastic\\": "src" + "Ziptastic\\": "src" } }, "minimum-stability": "stable" diff --git a/demo.php b/demo.php deleted file mode 100644 index 554ca99..0000000 --- a/demo.php +++ /dev/null @@ -1,7 +0,0 @@ -lookup(48038)); diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..bc5a589 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,113 @@ +service = $service; + $this->apiKey = $apiKey; + $this->countryCode = $countryCode; + } + + /** + * Create a client instance with the default service. + * + * @param string|null $apiKey + * @param string|null $countryCode + * @return Lookup + */ + public static function create( + string $apiKey = null, + string $countryCode = 'US' + ): self { + $service = new CurlService; + return new self($service, $apiKey, $countryCode); + } + + /** + * Lookup locale information by a postal code. + * + * @param string $zipCode The lookup postal code. + * @return ResponseItem[] + */ + public function forward(string $postalCode): array + { + $url = sprintf( + self::ZIPTASTIC_LOOKUP_URL, + $this->countryCode, + (string) $postalCode + ); + + return $this->request($url); + } + + /** + * Lookup locale information by a set of coordinates. + * + * @param float $latitude The lookup centerpoint latitude. + * @param float $longitude The lookup centerpoint longitude. + * @param integer $radius The search radius, in meters. + * @return ResponseItem[] + */ + public function reverse(float $latitude, float $longitude, int $radius): array + { + $url = sprintf( + self::ZIPTASTIC_REVERSE_URL, + $latitude, + $longitude, + $radius + ); + + return $this->request($url); + } + + /** + * Make a request to a given URI with the ziptastic API key. + * + * @param string $url + * @return ResponseItem[] + */ + private function request(string $url) + { + $res = $this->service->get($url, $this->apiKey); + + $collection = []; + foreach ($res as $result) { + $collection[] = new ResponseItem($result); + } + + return $collection; + } +} diff --git a/src/Exception.php b/src/Exception.php index 8d06dda..04cf2cd 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -1,6 +1,6 @@ service = $service; - $this->apiKey = $apiKey; - $this->countryCode = $countryCode; - } - - /** - * Create a client instance with the default service. - * - * @param ?string $apiKey - * @param ?string $countryCode - * @return Lookup - */ - public static function create(string $apiKey = null, string $countryCode = 'US'): self - { - $service = new CurlService; - return new self($service, $apiKey, $countryCode); - } - - /** - * Get information on given $zipCode - * @param string $zipCode - * @return LookupModel[] - */ - public function lookup($zipCode): array - { - $url = sprintf(self::ZIPTASTIC_LOOKUP_URL, $this->countryCode, (string) $zipCode); - $res = $this->service->get($url, $this->apiKey); - - $collection = []; - foreach ($res as $result) { - $collection[] = new LookupModel($result); - } - - return $collection; - } -} diff --git a/src/LookupModel.php b/src/ResponseItem.php similarity index 94% rename from src/LookupModel.php rename to src/ResponseItem.php index 6e51cc8..6c50580 100644 --- a/src/LookupModel.php +++ b/src/ResponseItem.php @@ -1,8 +1,11 @@ curl_close($handle); if (json_last_error() !== JSON_ERROR_NONE) { - throw new Exception('Could not parse response as json'); + throw new Exception(sprintf( + 'Could not parse response as json. Reason: %s', + json_last_error_msg() + )); } if ($statusCode !== 200 && isset($res['message'])) { diff --git a/src/Service/ServiceInterface.php b/src/Service/ServiceInterface.php index b1fe71e..f5aeab8 100644 --- a/src/Service/ServiceInterface.php +++ b/src/Service/ServiceInterface.php @@ -1,6 +1,6 @@ - 'Macomb', @@ -17,7 +17,7 @@ class LookupTest extends TestCase 'timezone' => 'America/Detroit' ]; - public function testLookup() + public function testFormward() { $res = function() { return [$this->stub]; @@ -25,8 +25,23 @@ public function testLookup() $service = new servicestub($res); - $lookup = new Lookup($service, '123'); - $l = $lookup->lookup(48038); + $client = new Client($service, '123'); + $l = $client->forward(48038); + + $this->assertEquals(1, count($l)); + $this->assertEquals($this->stub['city'], $l[0]->city()); + } + + public function testReverse() + { + $res = function() { + return [$this->stub]; + }; + + $service = new servicestub($res); + + $client = new Client($service, '123'); + $l = $client->reverse(100.10, 200.20, 1); $this->assertEquals(1, count($l)); $this->assertEquals($this->stub['city'], $l[0]->city()); @@ -40,8 +55,8 @@ public function testCollection() $service = new servicestub($res); - $lookup = new Lookup($service, '123'); - $l = $lookup->lookup(48038); + $client = new Client($service, '123'); + $l = $client->forward(48038); $this->assertEquals(2, count($l)); $this->assertEquals($this->stub['city'], $l[0]->city()); @@ -50,8 +65,8 @@ public function testCollection() public function testStatic() { - $lookup = Lookup::create('123'); - $this->assertInstanceOf(Lookup::class, $lookup); + $client = Client::create('123'); + $this->assertInstanceOf(client::class, $client); } } diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 01a31d4..f1652c1 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -1,7 +1,7 @@ apiKey); - $l = $lookup->lookup(48038); + $lookup = Client::create($this->apiKey); + $l = $lookup->forward(48038); + + foreach ($l as $model) { + $this->assertInternalType('string', $model->county()); + $this->assertInternalType('string', $model->city()); + $this->assertInternalType('string', $model->state()); + $this->assertInternalType('string', $model->stateShort()); + $this->assertInternalType('string', $model->postalCode()); + $this->assertInternalType('double', $model->latitude()); + $this->assertInternalType('double', $model->longitude()); + + $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); + } + } + + public function testReverseLookup() + { + $this->markTestSkipped('404 for some reason.'); + + $lookup = Client::create($this->apiKey); + $l = $lookup->reverse(42.331427, -83.0457538, 1000); foreach ($l as $model) { $this->assertInternalType('string', $model->county()); diff --git a/tests/LookupModelTest.php b/tests/ResponseItemTest.php similarity index 90% rename from tests/LookupModelTest.php rename to tests/ResponseItemTest.php index 8295b02..2a20765 100644 --- a/tests/LookupModelTest.php +++ b/tests/ResponseItemTest.php @@ -1,9 +1,9 @@ 'America/Detroit' ]; - $model = new LookupModel($data); + $model = new ResponseItem($data); $this->assertEquals($model->county(), $data['county']); $this->assertEquals($model->city(), $data['city']); @@ -34,7 +34,7 @@ public function testConstructor() public function testNullConstructor() { - $model = new LookupModel([]); + $model = new ResponseItem([]); $this->assertNull($model->county()); $this->assertNull($model->city()); $this->assertNull($model->state()); diff --git a/tests/Service/CurlServiceTest.php b/tests/Service/CurlServiceTest.php index c43ea5f..24d91bd 100644 --- a/tests/Service/CurlServiceTest.php +++ b/tests/Service/CurlServiceTest.php @@ -1,7 +1,7 @@ Date: Mon, 29 Oct 2018 10:43:36 -0400 Subject: [PATCH 4/8] Replace HTTP handler --- README.md | 22 ++----- composer.json | 9 ++- src/Client.php | 99 ++++++++++++++++++++++------ src/Service/CurlService.php | 86 ------------------------ src/Service/ServiceInterface.php | 6 -- tests/ClientTest.php | 61 ++++++------------ tests/IntegrationTest.php | 14 +++- tests/Service/CurlServiceTest.php | 104 ------------------------------ 8 files changed, 125 insertions(+), 276 deletions(-) delete mode 100644 src/Service/CurlService.php delete mode 100644 src/Service/ServiceInterface.php delete mode 100644 tests/Service/CurlServiceTest.php diff --git a/README.md b/README.md index 89418ff..94269af 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,13 @@ composer require ziptastic/ziptastic ## Usage -The simplest usage defaults to the provided Curl service: +Ziptastic PHP relies on [HTTPlug](http://httplug.io/) to make API requests. +HTTPlug is an abstraction which allows you to choose from any one of a large +number of HTTP clients, including the client you might already have installed. -```php -=7.1" + "php": ">=7.1", + "php-http/client-implementation": "^1.0" }, "require-dev": { "phpunit/phpunit": "^6.0", - "codeclimate/php-test-reporter": "dev-master" + "codeclimate/php-test-reporter": "dev-master", + "php-http/mock-client": "^1.0", + "php-http/message": "^1.0", + "php-http/curl-client": "^1.7", + "guzzlehttp/psr7": "^1.4" }, "license": "MIT", "authors": [ diff --git a/src/Client.php b/src/Client.php index bc5a589..5a53e62 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,18 +2,35 @@ namespace Ziptastic; -use Ziptastic\Service\CurlService; -use Ziptastic\Service\ServiceInterface; +use Http\Client\HttpClient; +use Http\Discovery\HttpClientDiscovery; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Message\MessageFactory; +/** + * The Ziptastic API class. + * + * Example: + * ``` + * use Ziptastic\Client; + * + * $ziptastic = Client::create($myApiKey); + * ``` + */ class Client { - const ZIPTASTIC_LOOKUP_URL = 'https://zip.getziptastic.com/v3/%s/%s/'; - const ZIPTASTIC_REVERSE_URL = 'https://zip.getziptastic.com/v3/reverse/%s/%s/%s/'; + const ZIPTASTIC_LOOKUP_URL = 'https://zip.getziptastic.com/v3/%s/%s'; + const ZIPTASTIC_REVERSE_URL = 'https://zip.getziptastic.com/v3/reverse/%s/%s/%s'; /** - * @var ServiceInterface; + * @var HttpClient; */ - private $service; + private $http; + + /** + * @var MessageFactory + */ + private $messageFactory; /** * @var string @@ -26,39 +43,57 @@ class Client private $countryCode; /** - * @param ServiceInterface $service + * @param HttpClient $service * @param string|null $apiKey API Key (For non-free accounts) * @param string|null $countryCode 2-character country code. Currently only * supports "US" */ public function __construct( - ServiceInterface $service, + HttpClient $http, + MessageFactory $messageFactory, string $apiKey = null, string $countryCode = 'US' ) { - $this->service = $service; + $this->http = $http; + $this->messageFactory = $messageFactory; $this->apiKey = $apiKey; $this->countryCode = $countryCode; } /** - * Create a client instance with the default service. + * Create a client instance with the default HTTP handler. + * + * Example: + * ``` + * $ziptastic = Client::create($myApiKey); + * ``` * - * @param string|null $apiKey - * @param string|null $countryCode - * @return Lookup + * @param string|null $apiKey API Key (For non-free accounts) + * @param string|null $countryCode 2-character country code. Currently only + * supports "US" + * @return Client */ public static function create( string $apiKey = null, string $countryCode = 'US' ): self { - $service = new CurlService; - return new self($service, $apiKey, $countryCode); + $http = HttpClientDiscovery::find(); + $messageFactory = MessageFactoryDiscovery::find(); + + return new self($http, $messageFactory, $apiKey, $countryCode); } /** * Lookup locale information by a postal code. * + * Example: + * ``` + * $result = $ziptastic->forward('48226'); + * foreach ($result as $item) { + * echo $item->postalCode() . PHP_EOL; + * } + * ``` + * * @param string $zipCode The lookup postal code. * @return ResponseItem[] */ @@ -76,12 +111,20 @@ public function forward(string $postalCode): array /** * Lookup locale information by a set of coordinates. * + * Example: + * ``` + * $result = $ziptastic->reverse(42.331427, -83.0457538, 1000); + * foreach ($result as $item) { + * echo $item->postalCode() . PHP_EOL; + * } + * ``` + * * @param float $latitude The lookup centerpoint latitude. * @param float $longitude The lookup centerpoint longitude. - * @param integer $radius The search radius, in meters. + * @param integer $radius The search radius, in meters. Defaults to `1000`. * @return ResponseItem[] */ - public function reverse(float $latitude, float $longitude, int $radius): array + public function reverse(float $latitude, float $longitude, int $radius = 1000): array { $url = sprintf( self::ZIPTASTIC_REVERSE_URL, @@ -101,10 +144,28 @@ public function reverse(float $latitude, float $longitude, int $radius): array */ private function request(string $url) { - $res = $this->service->get($url, $this->apiKey); + try { + $res = $this->http->sendRequest( + $this->messageFactory->createRequest('GET', $url, [ + 'x-key' => $this->apiKey + ]) + ); + } catch (\Exception $e) { + throw new Exception('An error occurred: '. $e->getMessage(), $e->getCode(), $e); + } + + $body = json_decode($res->getBody()->getContents(), true); + + if ($res->getStatusCode() !== 200) { + $message = isset($body['message']) + ? $body['message'] + : 'An error occurred'; + + throw new Exception($message, $res->getStatusCode()); + } $collection = []; - foreach ($res as $result) { + foreach ($body as $result) { $collection[] = new ResponseItem($result); } diff --git a/src/Service/CurlService.php b/src/Service/CurlService.php deleted file mode 100644 index 6ea99fd..0000000 --- a/src/Service/CurlService.php +++ /dev/null @@ -1,86 +0,0 @@ -curl_init(); - $this->curl_setopt($handle, CURLOPT_URL, $url); - $this->curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); - $this->curl_setopt($handle, CURLOPT_HTTPHEADER, [ - sprintf("x-key: %s", $apiKey) - ]); - - $response = $this->curl_exec($handle); - - $res = json_decode(trim($response), true); - $statusCode = $this->curl_getinfo($handle, CURLINFO_HTTP_CODE); - $this->curl_close($handle); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new Exception(sprintf( - 'Could not parse response as json. Reason: %s', - json_last_error_msg() - )); - } - - if ($statusCode !== 200 && isset($res['message'])) { - throw new Exception($res['message']); - } - - else if ($statusCode !== 200) { - throw new Exception('An error occurred'); - } - - return $res; - } - - /** These are for mocking in the unit tests **/ - - /** - * @codeCoverageIgnore - * @SuppressWarnings(PHPMD) - */ - protected function curl_init() - { - return curl_init(); - } - - /** - * @codeCoverageIgnore - * @SuppressWarnings(PHPMD) - */ - protected function curl_setopt($handle, $name, $opt) - { - return curl_setopt($handle, $name, $opt); - } - - /** - * @codeCoverageIgnore - * @SuppressWarnings(PHPMD) - */ - protected function curl_getinfo($handle, $name) - { - return curl_getinfo($handle, $name); - } - - /** - * @codeCoverageIgnore - * @SuppressWarnings(PHPMD) - */ - protected function curl_exec($handle) - { - return curl_exec($handle); - } - - /** - * @codeCoverageIgnore - * @SuppressWarnings(PHPMD) - */ - protected function curl_close($handle) - { - return curl_close($handle); - } -} diff --git a/src/Service/ServiceInterface.php b/src/Service/ServiceInterface.php deleted file mode 100644 index f5aeab8..0000000 --- a/src/Service/ServiceInterface.php +++ /dev/null @@ -1,6 +0,0 @@ - 'Macomb', 'city' => 'Clinton Township', @@ -17,46 +23,32 @@ class ClientTest extends TestCase 'timezone' => 'America/Detroit' ]; - public function testFormward() + public function setUp() { - $res = function() { - return [$this->stub]; - }; + $http = new MockClient; + $response = new Response(200, [], stream_for(json_encode([$this->stub, $this->stub]))); + $http->setDefaultResponse($response); - $service = new servicestub($res); + $this->client = new Client($http, MessageFactoryDiscovery::find(), '123'); + } - $client = new Client($service, '123'); - $l = $client->forward(48038); + public function testForward() + { + $l = $this->client->forward(48038); - $this->assertEquals(1, count($l)); $this->assertEquals($this->stub['city'], $l[0]->city()); } public function testReverse() { - $res = function() { - return [$this->stub]; - }; - - $service = new servicestub($res); + $l = $this->client->reverse(100.10, 200.20, 1); - $client = new Client($service, '123'); - $l = $client->reverse(100.10, 200.20, 1); - - $this->assertEquals(1, count($l)); $this->assertEquals($this->stub['city'], $l[0]->city()); } public function testCollection() { - $res = function() { - return [$this->stub, $this->stub]; - }; - - $service = new servicestub($res); - - $client = new Client($service, '123'); - $l = $client->forward(48038); + $l = $this->client->forward(48038); $this->assertEquals(2, count($l)); $this->assertEquals($this->stub['city'], $l[0]->city()); @@ -66,21 +58,6 @@ public function testCollection() public function testStatic() { $client = Client::create('123'); - $this->assertInstanceOf(client::class, $client); - } -} - -class servicestub implements ServiceInterface -{ - private $res; - - public function __construct(callable $res) - { - $this->res = $res; - } - - public function get($url, $headers) - { - return call_user_func($this->res); + $this->assertInstanceOf(Client::class, $client); } } diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index f1652c1..f74d98d 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -3,6 +3,9 @@ use PHPUnit\Framework\TestCase; use Ziptastic\Client; +/** + * @group integration + */ class IntegrationTest extends TestCase { private $apiKey; @@ -36,8 +39,6 @@ public function testForwardLookup() public function testReverseLookup() { - $this->markTestSkipped('404 for some reason.'); - $lookup = Client::create($this->apiKey); $l = $lookup->reverse(42.331427, -83.0457538, 1000); @@ -53,4 +54,13 @@ public function testReverseLookup() $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); } } + + /** + * @expectedException Ziptastic\Exception + */ + public function testException() + { + $lookup = Client::create($this->apiKey); + $lookup->forward('hello'); + } } diff --git a/tests/Service/CurlServiceTest.php b/tests/Service/CurlServiceTest.php deleted file mode 100644 index 24d91bd..0000000 --- a/tests/Service/CurlServiceTest.php +++ /dev/null @@ -1,104 +0,0 @@ - 'bar']); }; - $service = new curlservicestub($res, 200); - - $url = 'http://johnpedrie.com'; - $l = $service->get($url, '123'); - $this->assertEquals($service->opts[CURLOPT_URL], $url); - $this->assertEquals($service->opts[CURLOPT_HTTPHEADER], [ - 'x-key: 123' - ]); - - $this->assertEquals($l['foo'], 'bar'); - } - - - public function testGetMalformed() - { - $this->expectException( - Exception::class, - 'Could not parse response as json' - ); - - $res = function() { return json_encode(['foo' => 'bar']) . 'foo'; }; - $service = new curlservicestub($res, 200); - - $url = 'http://johnpedrie.com'; - $l = $service->get($url, '123'); - } - - public function testGetInvalidStatusWithMessage() - { - $this->expectException( - Exception::class, - 'bad' - ); - - $res = function() { return json_encode(['foo' => 'bar', 'message' => 'bad' ]); }; - $service = new curlservicestub($res, 500); - - $url = 'http://johnpedrie.com'; - $l = $service->get($url, '123'); - } - - public function testGetInvalidStatusWithoutMessage() - { - $this->expectException( - Exception::class, - 'An error occurred' - ); - - $res = function() { return json_encode(['foo' => 'bar' ]); }; - $service = new curlservicestub($res, 500); - - $url = 'http://johnpedrie.com'; - $l = $service->get($url, '123'); - } -} - -class curlservicestub extends Ziptastic\Service\CurlService -{ - private $res; - private $statusCode; - public $opts = []; - - public function __construct(callable $res, $statusCode) - { - $this->res = $res; - $this->statusCode = $statusCode; - } - - protected function curl_init() - { - return; - } - - protected function curl_setopt($ch, $name, $opt) - { - $this->opts[$name] = $opt; - return; - } - - protected function curl_getinfo($ch, $name) - { - return $this->statusCode; - } - - protected function curl_exec($ch) - { - return call_user_func($this->res); - } - - protected function curl_close($ch) - { - return; - } -} From 2b7abd5dbb983124ab6decee8a1566760c204a18 Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Mon, 29 Oct 2018 10:51:43 -0400 Subject: [PATCH 5/8] Fix coverage report --- .gitignore | 2 +- phpunit.xml | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 95d08c8..c92f7f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ vendor composer.lock .DS_Store -build +clover.xml diff --git a/phpunit.xml b/phpunit.xml index cf027a3..7c1c18d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,10 +7,16 @@ + + + ./src + ./vendor + ./tests + + + - - + From 8ec67871a9e64dd305ac13e3c5b4a7ad9db44fab Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Mon, 29 Oct 2018 11:27:00 -0400 Subject: [PATCH 6/8] miscellaneous --- .gitignore | 1 + .travis.yml | 2 -- README.md | 12 +++++++++--- src/Client.php | 9 ++++++--- src/Exception.php | 3 +++ src/ResponseItem.php | 9 ++++++++- tests/ResponseItemTest.php | 9 +++++++++ 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index c92f7f5..b070224 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor composer.lock .DS_Store clover.xml +build diff --git a/.travis.yml b/.travis.yml index d839d50..45ebdab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: php php: - 7.1 - 7.2 -install: - - mkdir -p build/logs before_script: - composer self-update - composer install --prefer-source --no-interaction --dev diff --git a/README.md b/README.md index 94269af..f5ab1ac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ziptastic PHP -[![Build Status](https://travis-ci.org/Ziptastic/ziptastic-php.svg)](https://travis-ci.org/Ziptastic/ziptastic-php) [![Code Climate](https://codeclimate.com/github/Ziptastic/ziptastic-php/badges/gpa.svg)](https://codeclimate.com/github/Ziptastic/ziptastic-php) [![Test Coverage](https://codeclimate.com/github/Ziptastic/ziptastic-php/badges/coverage.svg)](https://codeclimate.com/github/Ziptastic/ziptastic-php/coverage) +[![Latest Stable Version](https://poser.pugx.org/ziptastic/ziptastic/v/stable)](https://packagist.org/packages/ziptastic/ziptastic) [![Build Status](https://travis-ci.org/Ziptastic/ziptastic-php.svg)](https://travis-ci.org/Ziptastic/ziptastic-php) [![Test Coverage](https://codeclimate.com/github/Ziptastic/ziptastic-php/badges/coverage.svg)](https://codeclimate.com/github/Ziptastic/ziptastic-php/coverage) This library is a simple interface for the [Ziptastic API](https://www.getziptastic.com/). @@ -14,8 +14,6 @@ Ziptastic PHP can be installed via composer: composer require ziptastic/ziptastic ```` -## Usage - Ziptastic PHP relies on [HTTPlug](http://httplug.io/) to make API requests. HTTPlug is an abstraction which allows you to choose from any one of a large number of HTTP clients, including the client you might already have installed. @@ -24,6 +22,14 @@ For more information on getting started with HTTPlug, please refer to the [HTTPlug for library users](http://docs.php-http.org/en/latest/httplug/users.html) documentation. +To just get moving right now, install a couple common dependencies: + +``` +composer require php-http/curl-client guzzlehttp/psr7 php-http/message +``` + +## Usage + ```php countryCode, - (string) $postalCode + $postalCode ); return $this->request($url); @@ -142,7 +145,7 @@ public function reverse(float $latitude, float $longitude, int $radius = 1000): * @param string $url * @return ResponseItem[] */ - private function request(string $url) + private function request(string $url): array { try { $res = $this->http->sendRequest( diff --git a/src/Exception.php b/src/Exception.php index 04cf2cd..1a943f2 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -2,5 +2,8 @@ namespace Ziptastic; +/** + * Represents an error case in the Ziptastic API. + */ class Exception extends \Exception {} diff --git a/src/ResponseItem.php b/src/ResponseItem.php index 6c50580..a6bdc94 100644 --- a/src/ResponseItem.php +++ b/src/ResponseItem.php @@ -1,5 +1,7 @@ data['timezone']) { - $this->data['timezone'] = new \DateTimeZone($this->data['timezone']); + // If the timezone is not valid, keep null. + try { + $this->data['timezone'] = new \DateTimeZone($this->data['timezone']); + } catch (\Exception $e) { + $this->data['timezone'] = null; + } } } diff --git a/tests/ResponseItemTest.php b/tests/ResponseItemTest.php index 2a20765..1863325 100644 --- a/tests/ResponseItemTest.php +++ b/tests/ResponseItemTest.php @@ -44,4 +44,13 @@ public function testNullConstructor() $this->assertNull($model->longitude()); $this->assertNull($model->timezone()); } + + public function testInvalidTimezone() + { + $model = new ResponseItem([ + 'timezone' => 'n/a' + ]); + + $this->assertNull($model->timezone()); + } } From 4593f2eea676c9d03547500959aa64f95c4f9271 Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Mon, 29 Oct 2018 12:16:17 -0400 Subject: [PATCH 7/8] Remove ResponseItem class --- README.md | 22 ++++---- src/Client.php | 18 +++++-- src/Exception.php | 2 +- src/ResponseItem.php | 101 ------------------------------------- tests/ClientTest.php | 8 +-- tests/IntegrationTest.php | 39 +++++++------- tests/ResponseItemTest.php | 56 -------------------- 7 files changed, 46 insertions(+), 200 deletions(-) delete mode 100644 src/ResponseItem.php delete mode 100644 tests/ResponseItemTest.php diff --git a/README.md b/README.md index f5ab1ac..bb84579 100644 --- a/README.md +++ b/README.md @@ -48,22 +48,22 @@ $result = $z->forward(48038); $result = $z->reverse(42.331427, -83.0457538, 1000); ``` -Results are returned as a collection of class LookupModel: +Results are returned as a list of arrays: ```php county(); // Macomb -echo $lookup->city(); // Clinton Township -echo $lookup->state(); // Michigan -echo $lookup->stateShort(); // MI -echo $lookup->postalCode(); // 48038 -echo $lookup->latitude(); // 42.5868882 -echo $lookup->longitude(); // -82.9195514 - -// timezone() returns an instance of \DateTimeZone -echo $lookup->timezone()->getName(); // America/Detroit +echo $lookup['county']; // Macomb +echo $lookup['city']; // Clinton Township +echo $lookup['state']; // Michigan +echo $lookup['state_short']; // MI +echo $lookup['postal_code']; // 48038 +echo $lookup['latitude']; // 42.5868882 +echo $lookup['longitude']; // -82.9195514 + +// Timezones are represented by an instance of \DateTimeZone +echo $lookup['timezone']->getName(); // America/Detroit ``` ### PHP 5 diff --git a/src/Client.php b/src/Client.php index bc31e1a..a15c853 100644 --- a/src/Client.php +++ b/src/Client.php @@ -98,7 +98,7 @@ public static function create( * ``` * * @param string $zipCode The lookup postal code. - * @return ResponseItem[] + * @return array[] A list of arrays containing locale data. */ public function forward(string $postalCode): array { @@ -116,7 +116,7 @@ public function forward(string $postalCode): array * * Example: * ``` - * $result = $ziptastic->reverse(42.331427, -83.0457538, 1000); + * $result = $ziptastic->reverse(42.331427, -83.0457538); * foreach ($result as $item) { * echo $item->postalCode() . PHP_EOL; * } @@ -125,7 +125,7 @@ public function forward(string $postalCode): array * @param float $latitude The lookup centerpoint latitude. * @param float $longitude The lookup centerpoint longitude. * @param integer $radius The search radius, in meters. Defaults to `1000`. - * @return ResponseItem[] + * @return array[] A list of arrays containing locale data. */ public function reverse(float $latitude, float $longitude, int $radius = 1000): array { @@ -143,7 +143,7 @@ public function reverse(float $latitude, float $longitude, int $radius = 1000): * Make a request to a given URI with the ziptastic API key. * * @param string $url - * @return ResponseItem[] + * @return array[] A list of arrays containing locale data. */ private function request(string $url): array { @@ -169,7 +169,15 @@ private function request(string $url): array $collection = []; foreach ($body as $result) { - $collection[] = new ResponseItem($result); + // If the timezone is not valid, keep null. + try { + $result['timezone'] = new \DateTimeZone($result['timezone']); + } catch (\Exception $e) { + $result['timezone'] = null; + } + + $collection[] = $result; + } return $collection; diff --git a/src/Exception.php b/src/Exception.php index 1a943f2..a520a1f 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -5,5 +5,5 @@ /** * Represents an error case in the Ziptastic API. */ -class Exception extends \Exception +class Exception extends \RuntimeException {} diff --git a/src/ResponseItem.php b/src/ResponseItem.php deleted file mode 100644 index a6bdc94..0000000 --- a/src/ResponseItem.php +++ /dev/null @@ -1,101 +0,0 @@ -data = $data + [ - 'county' => null, - 'city' => null, - 'state' => null, - 'state_short' => null, - 'postal_code' => null, - 'latitude' => null, - 'longitude' => null, - 'timezone' => null, - ]; - - if ($this->data['timezone']) { - // If the timezone is not valid, keep null. - try { - $this->data['timezone'] = new \DateTimeZone($this->data['timezone']); - } catch (\Exception $e) { - $this->data['timezone'] = null; - } - } - } - - /** - * @return string - */ - public function county(): ?string - { - return $this->data['county']; - } - - /** - * @return string - */ - public function city(): ?string - { - return $this->data['city']; - } - - /** - * @return string - */ - public function state(): ?string - { - return $this->data['state']; - } - - /** - * @return string - */ - public function stateShort(): ?string - { - return $this->data['state_short']; - } - - /** - * @return string - */ - public function postalCode(): ?string - { - return $this->data['postal_code']; - } - - /** - * @return float - */ - public function latitude(): ?float - { - return $this->data['latitude']; - } - - /** - * @return float - */ - public function longitude(): ?float - { - return $this->data['longitude']; - } - - /** - * @return \DateTimeZone - */ - public function timezone(): ?\DateTimeZone - { - return $this->data['timezone']; - } -} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 2db2a82..025d5c8 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -36,14 +36,14 @@ public function testForward() { $l = $this->client->forward(48038); - $this->assertEquals($this->stub['city'], $l[0]->city()); + $this->assertEquals($this->stub['city'], $l[0]['city']); } public function testReverse() { $l = $this->client->reverse(100.10, 200.20, 1); - $this->assertEquals($this->stub['city'], $l[0]->city()); + $this->assertEquals($this->stub['city'], $l[0]['city']); } public function testCollection() @@ -51,8 +51,8 @@ public function testCollection() $l = $this->client->forward(48038); $this->assertEquals(2, count($l)); - $this->assertEquals($this->stub['city'], $l[0]->city()); - $this->assertEquals($this->stub['city'], $l[1]->city()); + $this->assertEquals($this->stub['city'], $l[0]['city']); + $this->assertEquals($this->stub['city'], $l[1]['city']); } public function testStatic() diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index f74d98d..4cf2a9e 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -24,17 +24,7 @@ public function testForwardLookup() $lookup = Client::create($this->apiKey); $l = $lookup->forward(48038); - foreach ($l as $model) { - $this->assertInternalType('string', $model->county()); - $this->assertInternalType('string', $model->city()); - $this->assertInternalType('string', $model->state()); - $this->assertInternalType('string', $model->stateShort()); - $this->assertInternalType('string', $model->postalCode()); - $this->assertInternalType('double', $model->latitude()); - $this->assertInternalType('double', $model->longitude()); - - $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); - } + $this->assertResult($l); } public function testReverseLookup() @@ -42,17 +32,7 @@ public function testReverseLookup() $lookup = Client::create($this->apiKey); $l = $lookup->reverse(42.331427, -83.0457538, 1000); - foreach ($l as $model) { - $this->assertInternalType('string', $model->county()); - $this->assertInternalType('string', $model->city()); - $this->assertInternalType('string', $model->state()); - $this->assertInternalType('string', $model->stateShort()); - $this->assertInternalType('string', $model->postalCode()); - $this->assertInternalType('double', $model->latitude()); - $this->assertInternalType('double', $model->longitude()); - - $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); - } + $this->assertResult($l); } /** @@ -63,4 +43,19 @@ public function testException() $lookup = Client::create($this->apiKey); $lookup->forward('hello'); } + + private function assertResult(array $l) + { + foreach ($l as $model) { + $this->assertInternalType('string', $model['county']); + $this->assertInternalType('string', $model['city']); + $this->assertInternalType('string', $model['state']); + $this->assertInternalType('string', $model['state_short']); + $this->assertInternalType('string', $model['postal_code']); + $this->assertInternalType('double', $model['latitude']); + $this->assertInternalType('double', $model['longitude']); + + $this->assertInstanceOf(\DateTimeZone::class, $model['timezone']); + } + } } diff --git a/tests/ResponseItemTest.php b/tests/ResponseItemTest.php deleted file mode 100644 index 1863325..0000000 --- a/tests/ResponseItemTest.php +++ /dev/null @@ -1,56 +0,0 @@ - 'Macomb', - 'city' => 'Clinton Township', - 'state' => 'Michigan', - 'state_short' => 'MI', - 'postal_code' => '48038', - 'latitude' => 42.5868882, - 'longitude' => -82.9195514, - 'timezone' => 'America/Detroit' - ]; - - $model = new ResponseItem($data); - - $this->assertEquals($model->county(), $data['county']); - $this->assertEquals($model->city(), $data['city']); - $this->assertEquals($model->state(), $data['state']); - $this->assertEquals($model->stateShort(), $data['state_short']); - $this->assertEquals($model->postalCode(), $data['postal_code']); - $this->assertEquals($model->latitude(), $data['latitude']); - $this->assertEquals($model->longitude(), $data['longitude']); - - $this->assertInstanceOf(\DateTimeZone::class, $model->timezone()); - $this->assertEquals($model->timezone()->getName(), 'America/Detroit'); - } - - public function testNullConstructor() - { - $model = new ResponseItem([]); - $this->assertNull($model->county()); - $this->assertNull($model->city()); - $this->assertNull($model->state()); - $this->assertNull($model->stateShort()); - $this->assertNull($model->postalCode()); - $this->assertNull($model->latitude()); - $this->assertNull($model->longitude()); - $this->assertNull($model->timezone()); - } - - public function testInvalidTimezone() - { - $model = new ResponseItem([ - 'timezone' => 'n/a' - ]); - - $this->assertNull($model->timezone()); - } -} From 20e4e00b3fb6588cccfb49a723641b40f096d33c Mon Sep 17 00:00:00 2001 From: John Pedrie Date: Tue, 30 Oct 2018 11:46:33 -0400 Subject: [PATCH 8/8] Require API Key --- src/Client.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Client.php b/src/Client.php index a15c853..43905a2 100644 --- a/src/Client.php +++ b/src/Client.php @@ -47,14 +47,14 @@ class Client /** * @param HttpClient $http An HTTP Client implementation. * @param MessageFactory $messageFactory An HTTP message factory implementation. - * @param string|null $apiKey API Key (For non-free accounts) + * @param string $apiKey Ziptastic API Key. * @param string|null $countryCode 2-character country code. Currently only - * supports "US" + * supports "US". */ public function __construct( HttpClient $http, MessageFactory $messageFactory, - string $apiKey = null, + string $apiKey, string $countryCode = 'US' ) { $this->http = $http; @@ -71,13 +71,13 @@ public function __construct( * $ziptastic = Client::create($myApiKey); * ``` * - * @param string|null $apiKey API Key (For non-free accounts) + * @param string $apiKey Ziptastic API Key. * @param string|null $countryCode 2-character country code. Currently only - * supports "US" + * supports "US". * @return Client */ public static function create( - string $apiKey = null, + string $apiKey, string $countryCode = 'US' ): self { $http = HttpClientDiscovery::find();