Skip to content
Merged
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: 0 additions & 3 deletions .coveralls.yml

This file was deleted.

29 changes: 29 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Tests

on:
push:
pull_request:

permissions:
contents: read

jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['7.1', '8.5']

steps:
- uses: actions/checkout@v5

- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer:${{ matrix.php == '7.1' && 'v2.2' || 'latest' }}

- run: composer update --prefer-dist --no-interaction --no-progress
- run: composer validate --strict
- run: vendor/bin/phpunit
12 changes: 0 additions & 12 deletions .travis.yml

This file was deleted.

38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# PHP Postcodes

[![Build Status](https://travis-ci.com/Jord-JD/php-postcodes.svg?branch=master)](https://travis-ci.com/Jord-JD/php-postcodes)
[![Coverage Status](https://coveralls.io/repos/github/Jord-JD/php-postcodes/badge.svg?branch=master)](https://coveralls.io/github/Jord-JD/php-postcodes?branch=master)
[![Tests](https://github.com/Jord-JD/php-postcodes/actions/workflows/tests.yml/badge.svg)](https://github.com/Jord-JD/php-postcodes/actions/workflows/tests.yml)
[![Packagist](https://img.shields.io/packagist/dt/jord-jd/php-postcodes.svg)](https://packagist.org/packages/jord-jd/php-postcodes/stats)

This library handles various UK postcode related tasks.
Expand All @@ -26,9 +25,9 @@ To install, just run the following composer command.
Using some of the data retrieval features provided by this library requires a postcode lookup service.
It currently supports the following postcode lookup services.

* Ideal Postcodes - https://ideal-postcodes.co.uk
* Postcode Anywhere (PCA Predict) - https://www.pcapredict.com/
* Postcodes.io - http://postcodes.io/
* [Ideal Postcodes](https://ideal-postcodes.co.uk)
* [Loqate](https://www.loqate.com/) (the provider formerly known as Postcode Anywhere/PCA Predict)
* [Postcodes.io](https://postcodes.io/)

Ideal Postcodes and Postcode Anywhere can return individual premises. Postcodes.io only provides postcode-level geographic and administrative data, so its `getAddressesByPostcode()` implementation returns a single `Address` object for the postcode rather than a list of premises.

Expand Down Expand Up @@ -68,6 +67,14 @@ $validated = \JordJD\Postcodes\Utils\Validator::validatePostcode('ST163DP');

Please note that the postcode validation is case insensitive.

You can also normalize user input to the conventional uppercase format. Invalid
non-string or incomplete input returns `null`.

```php
$postcode = \JordJD\Postcodes\Utils\Validator::normalizePostcode(" sw1a\t2aa ");
// SW1A 2AA
```

### Generate postcode

This library allows you generate a random, valid UK postcode. This makes use of the
Expand All @@ -85,3 +92,24 @@ $postcode = \JordJD\Postcodes\Utils\Generator::generatePostcode();
$outwardCode = \JordJD\Postcodes\Utils\Tokenizer::outward('ST163DP'); // Returns ST16
$inwardCode = \JordJD\Postcodes\Utils\Tokenizer::inward('ST163DP'); // Returns 3DP
```

## HTTP clients and errors

Ideal Postcodes, Loqate and Postcodes.io use their current HTTPS JSON APIs. Each
service accepts an optional Guzzle `ClientInterface` implementation as its
second constructor argument, which is useful for custom timeouts, proxies,
logging and tests.

```php
$client = new \GuzzleHttp\Client(['timeout' => 10]);
$service = new \JordJD\Postcodes\Objects\IdealPostcodes('API_KEY', $client);
```

Invalid postcodes throw `InvalidPostcodeException` before any API request is
made. Provider authentication, HTTP and response errors continue to throw an
exception with a descriptive message.

## Compatibility

PHP 7.1 through the current PHP 8.x releases are supported. Composer selects a
compatible maintained Guzzle and test-tool version for the PHP runtime in use.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
"license": "LGPL-3.0-only",
"require": {
"php": "^7.1||^8.0",
"guzzlehttp/guzzle": "^6.0||^7.0",
"guzzlehttp/guzzle": "^6.5.8||^7.4.5",
"fakerphp/faker": "^1.9.1",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.6"
"phpunit/phpunit": "^7.5||^9.6||^10.5||^11.5||^12.5"
},
"autoload": {
"psr-4": {
Expand All @@ -35,7 +35,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
"dev-master": "5.1-dev"
}
}
}
51 changes: 32 additions & 19 deletions src/Objects/IdealPostcodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,59 @@
namespace JordJD\Postcodes\Objects;

use JordJD\Postcodes\Interfaces\PostcodeServiceInterface;
use JordJD\Postcodes\Exceptions\InvalidPostcodeException;
use JordJD\Postcodes\Utils\Validator;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\ClientInterface;
use Psr\Http\Message\ResponseInterface;

class IdealPostcodes implements PostcodeServiceInterface
{
private $apiKey = null;
private $client = null;

public function __construct($apiKey)
public function __construct($apiKey, ClientInterface $client = null)
{
if (!$apiKey) {
throw new Exception('No Ideal Postcodes API key specified.');
}

$this->apiKey = $apiKey;

$headers = ['Authorization' => 'IDEALPOSTCODES api_key="'.$this->apiKey.'"'];

$this->client = new Client(['base_uri' => 'https://api.ideal-postcodes.co.uk/v1/', 'timeout' => 3.0, 'headers' => $headers]);
$this->client = $client ?: new Client(['timeout' => 3.0]);
}

public function getAddressesByPostcode($postcode)
{
$response = $this->client->request('GET', 'postcodes/'.$postcode);
if (!Validator::validatePostcode($postcode)) {
throw new InvalidPostcodeException('Post code provided is not valid');
}

$postcode = str_replace(' ', '', Validator::normalizePostcode($postcode));
$response = $this->client->request(
'GET',
'https://api.ideal-postcodes.co.uk/v1/postcodes/'.rawurlencode($postcode),
[
'headers' => ['Authorization' => 'api_key="'.$this->apiKey.'"'],
'http_errors' => false,
]
);

$result = $this->parseResponse($response);

return $result;
}

private function parseResponse(Response $response)
private function parseResponse(ResponseInterface $response)
{
if ($response->getStatusCode() != 200) {
throw new Exception('HTTP response code was not 200. Received HTTP reponse code: '.$response->getStatusCode().' ('.$response->getReasonPhrase().')');
}

$object = json_decode($response->getBody());
$object = json_decode((string) $response->getBody());

if (!$object) {
if (!is_object($object)) {
throw new Exception('Response JSON could not be decoded.');
}

Expand All @@ -62,16 +75,16 @@ private function parseResponse(Response $response)

foreach ($object->result as $idealPostcodesAddress) {
$address = new Address();
$address->companyName = $idealPostcodesAddress->organisation_name;
$address->line1 = $idealPostcodesAddress->line_1;
$address->line2 = $idealPostcodesAddress->line_2;
$address->line3 = $idealPostcodesAddress->line_3;
$address->townCity = $idealPostcodesAddress->post_town;
$address->county = $idealPostcodesAddress->county;
$address->country = $idealPostcodesAddress->country;
$address->postcode = $idealPostcodesAddress->postcode;
$address->longitude = $idealPostcodesAddress->longitude;
$address->latitude = $idealPostcodesAddress->latitude;
$address->companyName = isset($idealPostcodesAddress->organisation_name) ? $idealPostcodesAddress->organisation_name : '';
$address->line1 = isset($idealPostcodesAddress->line_1) ? $idealPostcodesAddress->line_1 : '';
$address->line2 = isset($idealPostcodesAddress->line_2) ? $idealPostcodesAddress->line_2 : '';
$address->line3 = isset($idealPostcodesAddress->line_3) ? $idealPostcodesAddress->line_3 : '';
$address->townCity = isset($idealPostcodesAddress->post_town) ? $idealPostcodesAddress->post_town : '';
$address->county = isset($idealPostcodesAddress->county) ? $idealPostcodesAddress->county : '';
$address->country = isset($idealPostcodesAddress->country) ? $idealPostcodesAddress->country : 'United Kingdom';
$address->postcode = isset($idealPostcodesAddress->postcode) ? $idealPostcodesAddress->postcode : '';
$address->longitude = isset($idealPostcodesAddress->longitude) ? $idealPostcodesAddress->longitude : null;
$address->latitude = isset($idealPostcodesAddress->latitude) ? $idealPostcodesAddress->latitude : null;
$addresses[] = $address;
}

Expand Down
Loading