isFullPostcode($query) => $this->lookupPostcode($query), $this->isOutcode($query) => $this->lookupOutcode($query), default => $this->lookupPlace($query), }; if ($result !== null) { Cache::put($cacheKey, $result, self::CACHE_TTL); } return $result; } private function isFullPostcode(string $query): bool { return (bool) preg_match('/^[A-Z]{1,2}[0-9][0-9A-Z]?\s*[0-9][A-Z]{2}$/i', $query); } private function isOutcode(string $query): bool { return (bool) preg_match('/^[A-Z]{1,2}[0-9][0-9A-Z]?$/i', $query); } private function lookupPostcode(string $postcode): ?LocationResult { $normalised = strtoupper(preg_replace('/\s+/', '', $postcode)); $url = self::BASE_URL.'/postcodes/'.$normalised; try { $response = $this->apiLogger->send('postcodes_io', 'GET', $url, fn () => Http::timeout(10)->get($url)); if (! $response->successful()) { return null; } $data = $response->json('result'); return new LocationResult( query: $postcode, displayName: $data['postcode'], lat: $data['latitude'], lng: $data['longitude'], ); } catch (Throwable $e) { Log::error('PostcodeService: postcode lookup failed', [ 'postcode' => $postcode, 'error' => $e->getMessage(), ]); return null; } } private function lookupOutcode(string $outcode): ?LocationResult { $normalised = strtoupper(trim($outcode)); $url = self::BASE_URL.'/outcodes/'.$normalised; try { $response = $this->apiLogger->send('postcodes_io', 'GET', $url, fn () => Http::timeout(10)->get($url)); if (! $response->successful()) { return null; } $data = $response->json('result'); return new LocationResult( query: $outcode, displayName: $data['outcode'], lat: $data['latitude'], lng: $data['longitude'], ); } catch (Throwable $e) { Log::error('PostcodeService: outcode lookup failed', [ 'outcode' => $outcode, 'error' => $e->getMessage(), ]); return null; } } private function lookupPlace(string $place): ?LocationResult { $url = self::BASE_URL.'/places'; $logUrl = $url.'?q='.urlencode($place).'&limit=1'; try { $response = $this->apiLogger->send('postcodes_io', 'GET', $logUrl, fn () => Http::timeout(10) ->get($url, ['q' => $place, 'limit' => 1])); if (! $response->successful()) { return null; } $results = $response->json('result'); if (empty($results)) { return null; } $data = $results[0]; return new LocationResult( query: $place, displayName: $data['name_1'], lat: $data['latitude'], lng: $data['longitude'], ); } catch (Throwable $e) { Log::error('PostcodeService: place lookup failed', [ 'place' => $place, 'error' => $e->getMessage(), ]); return null; } } }