Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
84 changes: 73 additions & 11 deletions lib/FH/PostcodeAPI/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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);
Expand All @@ -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 = [])
Expand All @@ -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)
{
Expand All @@ -165,4 +208,23 @@ private function parseResponse(ResponseInterface $response, RequestInterface $re

return $result;
}

/**
* @param string $url
* @throws InvalidUrlException
*/
private function validateUrl($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);
}
}
}
}
32 changes: 32 additions & 0 deletions lib/FH/PostcodeAPI/Exception/InvalidUrlException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace FH\PostcodeAPI\Exception;

/**
* @author Vlad Shut <[email protected]>
*/
class InvalidUrlException extends \Exception implements PostcodeApiExceptionInterface
{
/**
* @var string
*/
private $url;

/**
* @param string $url
*/
public function __construct($url)
{
parent::__construct("Invalid url provided '$url'");

$this->url = $url;
}

/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
}
101 changes: 91 additions & 10 deletions tests/FH/PostcodeAPI/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
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 GuzzleHttp\Psr7\Response;
use Http\Client\Exception;
use PHPUnit_Framework_TestCase;
use stdClass;

/**
* @author Gijs Nieuwenhuis <[email protected]>
*/
final class ClientTest extends \PHPUnit_Framework_TestCase
final class ClientTest extends PHPUnit_Framework_TestCase
{
/** @var string */
const POSTCODE_PATTERN = '/^[\d]{4}[\w]{2}$/i';
Expand All @@ -31,8 +38,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()
{
Expand All @@ -43,6 +58,12 @@ public function testRequestExceptionIsThrownWhenUsingAnInvalidApiKey()
$client->getAddresses();
}

/**
* @throws InvalidApiKeyException
* @throws ServerErrorException
* @throws CouldNotParseResponseException
* @throws Exception
*/
public function testListResourceReturnsAllAddressesWhenNoParamsAreSupplied()
{
$client = $this->createClient(
Expand All @@ -60,6 +81,12 @@ public function testListResourceReturnsAllAddressesWhenNoParamsAreSupplied()
$this->applyAddressFieldAreSetAndOfTheCorrectTypeAssertions($addresses[0]);
}

/**
* @throws CouldNotParseResponseException
* @throws Exception
* @throws InvalidApiKeyException
* @throws ServerErrorException
*/
public function testListResourceReturnsExpectedAddressWhenPostcodeAndNumberAreSupplied()
{
$client = $this->createClient(
Expand All @@ -80,6 +107,12 @@ public function testListResourceReturnsExpectedAddressWhenPostcodeAndNumberAreSu
$this->applyIsFreshheadsAddressAssertions($firstAddress);
}

/**
* @throws CouldNotParseResponseException
* @throws Exception
* @throws InvalidApiKeyException
* @throws ServerErrorException
*/
public function testExpectedAddressInformationIsReturnedFromDetailResource()
{
$client = $this->createClient(
Expand All @@ -94,8 +127,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')
Expand All @@ -104,6 +142,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
*
Expand All @@ -115,9 +197,9 @@ private function loadMockResponse($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');
Expand All @@ -139,18 +221,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

Expand All @@ -176,8 +258,7 @@ private function applyAddressFieldAreSetAndOfTheCorrectTypeAssertions(\stdClass
}

/**
* @param string $mockedResponses
*
* @param Response|string $mockedResponse
* @return Client
*/
private function createClient(Response $mockedResponse)
Expand Down