Skip to content

Check and look up postcodes in PHP

gatepost/postcode reads the postcodes that users type or paste, with no network call. Its client queries NIPOST’s gateway with your own API key. It needs PHP 8.1 or later, and it does not need the intl or mbstring extensions.

The first alpha is not published yet. This page will say when it is.

Terminal window
composer require gatepost/postcode:^0.1@alpha

Composer installs only stable releases unless the constraint allows an alpha.

Postcode::parse takes the text that a user typed, and returns a result. It never throws.

<?php
use Gatepost\Postcode\Postcode;
$result = Postcode::parse('ek 01 a03 fk 01');
if ($result->isOk()) {
echo $result->value->canonical, "\n"; // EK-01-A03-FK-01
echo $result->value->display, "\n"; // EK 01 A03 FK 01
}

When isOk() is false, error->code names the first problem that parse found. The codes are the same in every Gatepost SDK. The TypeScript guide lists them. For a typo with a look-alike character, error->suggestion holds the fixed postcode.

<?php
use Gatepost\Postcode\Postcode;
$typo = Postcode::parse('EK-O1-A03-FK-01');
echo $typo->error?->code->value, "\n"; // bad_segment
echo $typo->error?->suggestion, "\n"; // EK-01-A03-FK-01

Show the suggestion as a question, and let the user accept it.

The client needs a PSR-18 HTTP client and a PSR-17 request factory, such as Guzzle 7.

Terminal window
composer require guzzlehttp/guzzle

Keep the key on a server, and read it from the environment. PSR-18 has no timeout, so the client cannot stop a slow request. Set the timeout of your HTTP client. With no timeout on the HTTP client, a request can wait without limit.

Pass the same value as timeoutMs. It does not set a limit. It tells the client how to read a failed attempt. An attempt that fails after timeoutMs or more gives timeout, and a faster failure gives network_error. One timeoutMs applies to every call. When you pass none, the client uses 8000 ms, and 15000 ms for autocomplete.

<?php
use Gatepost\Postcode\Client\PostcodeClient;
use Gatepost\Postcode\Client\PostcodeException;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
$client = new PostcodeClient(
new Client(['timeout' => 15]),
new HttpFactory(),
apiKey: getenv('NIPOST_API_KEY') ?: null,
timeoutMs: 15000,
);
try {
$result = $client->lookup('FC-01-Z99-ZZ-01');
echo $result->valid ? 'valid' : 'not found', "\n"; // valid
} catch (PostcodeException $error) {
echo $error->errorCode()->value, "\n";
}

An empty string for apiKey throws an InvalidArgumentException. With null, the client sends no key, and the gateway answers 401.

The examples on this page use FC-01-Z99-ZZ-01, a synthetic postcode. They show the answers of Gatepost’s mock gateway, which runs them in CI. The real gateway does not know this postcode.

lookup checks the postcode with Postcode::parse first, and sends no request for a bad postcode. Pass level: 2 for more data, when your key allows it. Each failed call throws a PostcodeException. Its errorCode() gives the same codes as the TypeScript client.

reverse finds the postcode at a latitude and a longitude. autocomplete names the segment that the user is typing, and returns the gateway’s values for it.

<?php
use Gatepost\Postcode\Client\PostcodeClient;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
$client = new PostcodeClient(
new Client(['timeout' => 15]),
new HttpFactory(),
apiKey: getenv('NIPOST_API_KEY') ?: null,
timeoutMs: 15000,
);
$place = $client->reverse(9.0, 7.0);
echo $place->unit?->postcode->canonical, "\n"; // FC-01-Z99-ZZ-01
$typing = $client->autocomplete('fc 01 z');
echo $typing->segment->value, "\n"; // district

The client is synchronous. Each call waits for its result, and the client sends one request at a time.

Unofficial. Not made or endorsed by NIPOST.