Here, a class that calls postcodes.io
<?php
namespace App\Utility;
use App\Postcode;
use Illuminate\Support\Facades\Log;
use \Exception;
class PostcodeLookup
{
/**
* Lookups up postcodes using postcodes.io
* Status from https://status.ideal-postcodes.co.uk/
* returns 0,0 if service fails, and writes message to logfile.
* Uses database to cache previous lookups
*
* @param string $postcode a valid UK postcode
* @return array with lat and lon as keys values as string
*/
public function lookup(string $postcode): array
{
$postcode = strtoupper(str_replace(' ','',$postcode));
$result = cache()->remember('postcode'.$postcode, now()->addMinute(1), function () use($postcode) {
$dbresult = Postcode::where('postcode', $postcode)->select(['postcode', 'latitude', 'longitude'])->first();
if($dbresult) {
return $dbresult->toArray();
}
return null;
});
if($result){
if($result['longitude'] == 0) {
Log::warning("Postcode coordinates returned are 52,0 for `{$postcode}`");
}
return ['lat' => $result['latitude'], 'lon' => $result['longitude']];
}
try {
$result = $this->strategies($postcode);
} catch (Exception $e) {
Log::Error("Postcode Lookup failed for `{$postcode}`");
Postcode::create(['postcode' => $postcode, 'latitude' => 52, 'longitude' => 0]);
return ['lat' => 52, 'lon' => 0];
}
Postcode::create([
'postcode' => $postcode,
'latitude' => $result['lat'],
'longitude' => $result['lon'],
]);
return $result;
}
/**
* uses various strategies to resolve postcode to latlong
* returns the first result
*
* @param string $postcode The postcode string to find
* @return array lat,long
*/
public function strategies(string $postcode):array
{
if($result = $this->directLookup($postcode)) {
return $result;
}
if ($result = $this->terminatedLookup($postcode)) {
return $result;
}
if ($result = $this->partialLookup($postcode)) {
return $result;
}
Log::warning("No viable postcode lookup strategies for '$postcode'");
throw new Exception('No Viable Postcode Strategy');
}
/**
* performs exact match lookup for the postcode
*
* @param string $postcode
* @return array
*/
public function directLookup(string $postcode): ?array
{
try {
$result = json_decode(file_get_contents("https://api.postcodes.io/postcodes/$postcode"));
return ['lat' => (string) $result->result->latitude, 'lon' => (string) $result->result->longitude];
} catch (\Throwable $th) {
return null;
}
}
/**
* performs a search to see if the postcode was previously terminated
*
* @param string $postcode
* @return array
*/
public function terminatedLookup(string $postcode): ?array
{
try {
$result = json_decode(file_get_contents("https://api.postcodes.io/terminated_postcodes/$postcode"));
Log::info("Resolved failed postcode by looking in terminated codes for `$postcode`");
return ['lat' => (string) $result->result->latitude, 'lon' => (string) $result->result->longitude];
} catch (\Throwable $th) {
return null;
}
}
public function partialLookup($postcode)
{
$original = $postcode;
while(strlen($postcode)>3){
try {
$result = json_decode(file_get_contents("https://api.postcodes.io/postcodes/$postcode/autocomplete"));
} catch (\Throwable $th) {
return null;
}
$codes = $result->result;
if($codes) {
$resolve = $codes[0]; //unintelligently grabs the first from the stack
Log::info("Using partial postcode lookup. Aliased $original to $resolve ");
return $this->directLookup($resolve);
}
$postcode = substr($postcode, 0, -1);
}
return null;
}
}
You will see I use a table to store previous lookups so that the postcode can be resolved quickly if it has been checked before.
I'm sure you could adapt this to your needs