From 944d05d5854635b713c6869fa4602574b814600a Mon Sep 17 00:00:00 2001 From: Vladyslav Shut Date: Fri, 10 May 2019 14:41:47 +0300 Subject: [PATCH 1/3] Add public request() method to handle next link in pagination --- composer.json | 3 +- lib/FH/PostcodeAPI/Client.php | 84 ++++++++++++-- .../Exception/InvalidUrlException.php | 32 ++++++ tests/FH/PostcodeAPI/ClientTest.php | 104 ++++++++++++++++-- 4 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 lib/FH/PostcodeAPI/Exception/InvalidUrlException.php diff --git a/composer.json b/composer.json index 242dd97..2b9ea40 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "require": { "php": ">=5.4.0", "php-http/httplug": "^1.1", - "guzzlehttp/psr7": "^1.3" + "guzzlehttp/psr7": "^1.3", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^4.3.5|^5.0", diff --git a/lib/FH/PostcodeAPI/Client.php b/lib/FH/PostcodeAPI/Client.php index af080e8..fe566e0 100755 --- a/lib/FH/PostcodeAPI/Client.php +++ b/lib/FH/PostcodeAPI/Client.php @@ -4,11 +4,14 @@ use FH\PostcodeAPI\Exception\CouldNotParseResponseException; use FH\PostcodeAPI\Exception\InvalidApiKeyException; +use FH\PostcodeAPI\Exception\InvalidUrlException; use FH\PostcodeAPI\Exception\ServerErrorException; use GuzzleHttp\Psr7\Request; +use Http\Client\Exception; use Http\Client\HttpClient; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use stdClass; /** * Client library for postcodeapi.nu 2.0 web service. @@ -50,7 +53,11 @@ public function __construct(HttpClient $httpClient, $url = null) * @param string|null $number * @param int $from * - * @return \stdClass + * @return stdClass + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException */ public function getAddresses($postcode = null, $number = null, $from = 0) { @@ -64,7 +71,11 @@ public function getAddresses($postcode = null, $number = null, $from = 0) /** * @param string $id * - * @return \stdClass + * @return stdClass + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException */ public function getAddress($id) { @@ -74,7 +85,11 @@ public function getAddress($id) /** * @param string $postcode * - * @return \stdClass + * @return stdClass + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException */ public function getPostcodeDataByPostcode($postcode) { @@ -86,7 +101,11 @@ public function getPostcodeDataByPostcode($postcode) * @param string $longitude * @param string $sort * - * @return \stdClass + * @return stdClass + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException */ public function getPostcodesByCoordinates($latitude, $longitude, $sort = self::POSTCODES_SORT_DISTANCE) { @@ -99,18 +118,41 @@ public function getPostcodesByCoordinates($latitude, $longitude, $sort = self::P ]); } + /** + * Sends request to API using url and returns parsed response. + * Useful for pagination link. + * + * @param $url + * @return stdClass + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException + * @throws InvalidUrlException + */ + public function request($url) + { + $this->validateUrl($url); + $request = $this->createHttpGetRequest($url); + $response = $this->httpClient->sendRequest($request); + + return $this->parseResponse($response, $request); + } + /** * @param string $path * @param array $params * - * @return \stdClass + * @return stdClass * - * @throws RequestException + * @throws CouldNotParseResponseException + * @throws InvalidApiKeyException + * @throws ServerErrorException + * @throws Exception */ private function get($path, array $params = []) { $request = $this->createHttpGetRequest($this->buildUrl($path), $params); - $response = $this->httpClient->sendRequest($request); return $this->parseResponse($response, $request); @@ -126,10 +168,8 @@ private function buildUrl($path) } /** - * @param string $method * @param string $url - * @param array $queryParams - * + * @param array $params * @return Request */ private function createHttpGetRequest($url, array $params = []) @@ -142,9 +182,12 @@ private function createHttpGetRequest($url, array $params = []) /** * @param ResponseInterface $response * - * @return \stdClass + * @param RequestInterface $request + * @return stdClass * * @throws CouldNotParseResponseException + * @throws InvalidApiKeyException + * @throws ServerErrorException */ private function parseResponse(ResponseInterface $response, RequestInterface $request) { @@ -165,4 +208,23 @@ private function parseResponse(ResponseInterface $response, RequestInterface $re return $result; } + + /** + * @param string $url + * @throws InvalidUrlException + */ + private function validateUrl(string $url) + { + if (filter_var($url, FILTER_VALIDATE_URL) === false) { + throw new InvalidUrlException($url); + } + + $urlComponentsToCompare = [PHP_URL_HOST, PHP_URL_SCHEME]; + + foreach ($urlComponentsToCompare as $urlComponent) { + if (parse_url($url, $urlComponent) !== parse_url($this->url, $urlComponent)) { + throw new InvalidUrlException($url); + } + } + } } diff --git a/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php b/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php new file mode 100644 index 0000000..552c211 --- /dev/null +++ b/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php @@ -0,0 +1,32 @@ + + */ +class InvalidUrlException extends \Exception implements PostcodeApiExceptionInterface +{ + /** + * @var string + */ + private $url; + + /** + * @param string $url + */ + public function __construct(string $url) + { + parent::__construct("Invalid url provided '$url'"); + + $this->url = $url; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } +} diff --git a/tests/FH/PostcodeAPI/ClientTest.php b/tests/FH/PostcodeAPI/ClientTest.php index 86dc356..657ffbe 100755 --- a/tests/FH/PostcodeAPI/ClientTest.php +++ b/tests/FH/PostcodeAPI/ClientTest.php @@ -3,12 +3,20 @@ namespace FH\PostcodeAPI\Test; use FH\PostcodeAPI\Client; +use FH\PostcodeAPI\Exception\CouldNotParseResponseException; +use FH\PostcodeAPI\Exception\InvalidApiKeyException; +use FH\PostcodeAPI\Exception\InvalidUrlException; +use FH\PostcodeAPI\Exception\ServerErrorException; +use function GuzzleHttp\Psr7\parse_response; use GuzzleHttp\Psr7\Response; +use Http\Client\Exception; +use PHPUnit_Framework_TestCase; +use stdClass; /** * @author Gijs Nieuwenhuis */ -final class ClientTest extends \PHPUnit_Framework_TestCase +final class ClientTest extends PHPUnit_Framework_TestCase { /** @var string */ const POSTCODE_PATTERN = '/^[\d]{4}[\w]{2}$/i'; @@ -31,8 +39,16 @@ final class ClientTest extends \PHPUnit_Framework_TestCase /** @var string */ const FRESHHEADS_ADDRESS_ID = '0855200000061001'; + /** @var string */ + const FRESHHEADS_VALID_URL = 'https://api.postcodeapi.nu/v2/addresses/?postcode=4904ZR&from%5Bpostcode%5D=4904ZR&from%5Bid%5D=0826200000012452&from%5Bnumber%5D=95'; + /** * @expectedException FH\PostcodeAPI\Exception\InvalidApiKeyException + * + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException */ public function testRequestExceptionIsThrownWhenUsingAnInvalidApiKey() { @@ -43,6 +59,12 @@ public function testRequestExceptionIsThrownWhenUsingAnInvalidApiKey() $client->getAddresses(); } + /** + * @throws InvalidApiKeyException + * @throws ServerErrorException + * @throws CouldNotParseResponseException + * @throws Exception + */ public function testListResourceReturnsAllAddressesWhenNoParamsAreSupplied() { $client = $this->createClient( @@ -60,6 +82,12 @@ public function testListResourceReturnsAllAddressesWhenNoParamsAreSupplied() $this->applyAddressFieldAreSetAndOfTheCorrectTypeAssertions($addresses[0]); } + /** + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException + */ public function testListResourceReturnsExpectedAddressWhenPostcodeAndNumberAreSupplied() { $client = $this->createClient( @@ -80,6 +108,12 @@ public function testListResourceReturnsExpectedAddressWhenPostcodeAndNumberAreSu $this->applyIsFreshheadsAddressAssertions($firstAddress); } + /** + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException + */ public function testExpectedAddressInformationIsReturnedFromDetailResource() { $client = $this->createClient( @@ -94,8 +128,13 @@ public function testExpectedAddressInformationIsReturnedFromDetailResource() /** * @expectedException FH\PostcodeAPI\Exception\ServerErrorException + * + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException */ - public function testClientThrowsExceptionWhenInvalidInputIsSupplied() + public function testExpectedAddress() { $client = $this->createClient( $this->loadMockResponse('failed_list_with_invalid_postalcode_and_number') @@ -104,6 +143,50 @@ public function testClientThrowsExceptionWhenInvalidInputIsSupplied() $client->getAddresses('invalid_postcode', 'invalid_number'); } + /** + * @dataProvider invalidUrlsProvider + * @expectedException FH\PostcodeAPI\Exception\InvalidUrlException + * + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException + * @throws InvalidUrlException + */ + public function testClientThrowsExceptionWhenInvalidUrlIsSupplied($invalidUrl) + { + $client = $this->createClient( + $this->loadMockResponse('successful_list_without_filtering') + ); + + $client->request($invalidUrl); + } + + /** + * @throws CouldNotParseResponseException + * @throws Exception + * @throws InvalidApiKeyException + * @throws ServerErrorException + * @throws InvalidUrlException + */ + public function testExpectedNoExceptionsWhenValidNextLinkIsSupplied() + { + $client = $this->createClient( + $this->loadMockResponse('successful_list_without_filtering') + ); + + $client->request(self::FRESHHEADS_VALID_URL); + } + + public function invalidUrlsProvider() + { + return [ + ['invalid_url'], + ['https://api.postcodeapi.com/invalid-host'], + ['http://api.postcodeapi.nu/invalid-schema'], + ]; + } + /** * @param string $name * @@ -111,13 +194,13 @@ public function testClientThrowsExceptionWhenInvalidInputIsSupplied() */ private function loadMockResponse($name) { - return \GuzzleHttp\Psr7\parse_response(file_get_contents(__DIR__ . "/../../Mock/{$name}")); + return parse_response(file_get_contents(__DIR__ . "/../../Mock/{$name}")); } /** - * @param \stdClass $address + * @param stdClass $address */ - private function applyIsFreshheadsAddressAssertions(\stdClass $address) + private function applyIsFreshheadsAddressAssertions(stdClass $address) { static::assertSame(strtoupper($address->postcode), self::FRESHHEADS_POSTCODE, 'Incoming postcode did not match the expected postcode'); static::assertSame((string)$address->number, (string)self::FRESHHEADS_NUMBER, 'Incoming number did not match the expected number'); @@ -139,18 +222,18 @@ private function applyIsFreshheadsAddressAssertions(\stdClass $address) } /** - * @param \stdClass $response + * @param stdClass $response */ - private function applyAssertsToMakeSureAddressesArrayIsAvailableInResponse(\stdClass $response) + private function applyAssertsToMakeSureAddressesArrayIsAvailableInResponse(stdClass $response) { static::assertTrue(isset($response->_embedded->addresses)); static::assertTrue(is_array($response->_embedded->addresses)); } /** - * @param \stdClass $address + * @param stdClass $address */ - private function applyAddressFieldAreSetAndOfTheCorrectTypeAssertions(\stdClass $address) + private function applyAddressFieldAreSetAndOfTheCorrectTypeAssertions(stdClass $address) { // only test the availability of the most import fields and their values @@ -176,8 +259,7 @@ private function applyAddressFieldAreSetAndOfTheCorrectTypeAssertions(\stdClass } /** - * @param string $mockedResponses - * + * @param Response|string $mockedResponse * @return Client */ private function createClient(Response $mockedResponse) From bc27db8122d243f35334343cc2465f4b7eaee586 Mon Sep 17 00:00:00 2001 From: Vladyslav Shut Date: Fri, 10 May 2019 14:48:35 +0300 Subject: [PATCH 2/3] Add public request() method to handle next link in pagination --- tests/FH/PostcodeAPI/ClientTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/FH/PostcodeAPI/ClientTest.php b/tests/FH/PostcodeAPI/ClientTest.php index 657ffbe..bc45675 100755 --- a/tests/FH/PostcodeAPI/ClientTest.php +++ b/tests/FH/PostcodeAPI/ClientTest.php @@ -7,7 +7,6 @@ use FH\PostcodeAPI\Exception\InvalidApiKeyException; use FH\PostcodeAPI\Exception\InvalidUrlException; use FH\PostcodeAPI\Exception\ServerErrorException; -use function GuzzleHttp\Psr7\parse_response; use GuzzleHttp\Psr7\Response; use Http\Client\Exception; use PHPUnit_Framework_TestCase; @@ -194,7 +193,7 @@ public function invalidUrlsProvider() */ private function loadMockResponse($name) { - return parse_response(file_get_contents(__DIR__ . "/../../Mock/{$name}")); + return \GuzzleHttp\Psr7\parse_response(file_get_contents(__DIR__ . "/../../Mock/{$name}")); } /** From 7c73fd75f17b4f6c18c76f8ca0715de2f89ff9ab Mon Sep 17 00:00:00 2001 From: Vladyslav Shut Date: Fri, 10 May 2019 15:12:49 +0300 Subject: [PATCH 3/3] Add public request() method to handle next link in pagination --- lib/FH/PostcodeAPI/Client.php | 2 +- lib/FH/PostcodeAPI/Exception/InvalidUrlException.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/FH/PostcodeAPI/Client.php b/lib/FH/PostcodeAPI/Client.php index fe566e0..0f18a6b 100755 --- a/lib/FH/PostcodeAPI/Client.php +++ b/lib/FH/PostcodeAPI/Client.php @@ -213,7 +213,7 @@ private function parseResponse(ResponseInterface $response, RequestInterface $re * @param string $url * @throws InvalidUrlException */ - private function validateUrl(string $url) + private function validateUrl($url) { if (filter_var($url, FILTER_VALIDATE_URL) === false) { throw new InvalidUrlException($url); diff --git a/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php b/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php index 552c211..82ab179 100644 --- a/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php +++ b/lib/FH/PostcodeAPI/Exception/InvalidUrlException.php @@ -15,7 +15,7 @@ class InvalidUrlException extends \Exception implements PostcodeApiExceptionInte /** * @param string $url */ - public function __construct(string $url) + public function __construct($url) { parent::__construct("Invalid url provided '$url'");