findByZipCode($zipCode); } // public function calculateDistance( ?float $originLatitude, ?float $originLongitude, ?string $originZipCode, ?float $targetLatitude, ?float $targetLongitude, ?string $targetZipCode, ): ?float { $origin = $this->resolve($originLatitude, $originLongitude, $originZipCode); $target = $this->resolve($targetLatitude, $targetLongitude, $targetZipCode); $distance = DistanceService::calculate( $origin['latitude'] ?? null, $origin['longitude'] ?? null, $target['latitude'] ?? null, $target['longitude'] ?? null, ); return $distance; } public function findByZipCode(?string $zipCode): ?array { $zipCode = $this->normalize($zipCode); if ($zipCode === null) { return null; } if (array_key_exists($zipCode, $this->resolvedZipCodes)) { return $this->resolvedZipCodes[$zipCode]; } if ($this->restoreFromCache($zipCode)) { return $this->resolvedZipCodes[$zipCode]; } if (! $this->googleGeocodingIsConfigured()) { return $this->resolvedZipCodes[$zipCode] = null; } $coordinates = $this->requestCoordinates($zipCode); $this->remember($zipCode, $coordinates); return $coordinates; } public function preload(iterable $zipCodes): void { $pending = []; foreach ($zipCodes as $zipCode) { $zipCode = $this->normalize($zipCode); if ( $zipCode === null || array_key_exists($zipCode, $this->resolvedZipCodes) || $this->restoreFromCache($zipCode) ) { continue; } $pending[$zipCode] = $zipCode; } if ($pending === []) { return; } if (! $this->googleGeocodingIsConfigured()) { foreach ($pending as $zipCode) { $this->resolvedZipCodes[$zipCode] = null; } return; } Log::channel('google_geocoding')->info('ZipCodeCoordinates Google Geocoding lookup required', [ 'mode' => 'preload', 'pending_count' => count($pending), 'zip_codes_sample' => array_slice(array_values($pending), 0, 20), 'connect_timeout' => (int) config('services.google_geocoding.connect_timeout', 2), 'timeout' => (int) config('services.google_geocoding.timeout', 5), 'ip_resolve' => 'ipv4', ]); try { $baseUrl = rtrim((string) config('services.google_geocoding.base_url'), '/'); $startedAt = microtime(true); $responses = Http::pool(fn (Pool $pool) => array_map( fn (string $zipCode) => $pool ->as($zipCode) ->acceptJson() ->connectTimeout((int) config('services.google_geocoding.connect_timeout', 2)) ->timeout((int) config('services.google_geocoding.timeout', 5)) ->withOptions($this->curlForceIpv4Options()) ->get("{$baseUrl}/maps/api/geocode/json", $this->googleQuery($zipCode)), $pending, )); } catch (Throwable $exception) { Log::channel('google_geocoding')->warning('ZipCodeCoordinates Google Geocoding preload failed', [ 'pending_count' => count($pending), 'elapsed_ms' => isset($startedAt) ? round((microtime(true) - $startedAt) * 1000, 2) : null, 'error' => $exception->getMessage(), ]); $responses = []; } foreach ($pending as $zipCode) { $response = $responses[$zipCode] ?? null; $coordinates = $response instanceof Response ? $this->coordinatesFromResponse($response) : null; $this->remember($zipCode, $coordinates); } } // private function coordinatesFromResponse(Response $response): ?array { if (! $response->successful()) { return null; } if ($response->json('status') !== 'OK') { return null; } $latitude = $response->json('results.0.geometry.location.lat'); $longitude = $response->json('results.0.geometry.location.lng'); if (! is_numeric($latitude) || ! is_numeric($longitude)) { return null; } $latitude = (float) $latitude; $longitude = (float) $longitude; if ($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180) { return null; } return compact('latitude', 'longitude'); } private function curlForceIpv4Options(): array { if (! defined('CURLOPT_IPRESOLVE') || ! defined('CURL_IPRESOLVE_V4')) { return []; } return [ 'curl' => [ CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, ], ]; } private function googleGeocodingIsConfigured(): bool { return trim((string) config('services.google_geocoding.api_key')) !== ''; } private function googleQuery(string $zipCode): array { return [ 'components' => "postal_code:{$zipCode}|country:BR", 'language' => 'pt-BR', 'region' => 'br', 'key' => (string) config('services.google_geocoding.api_key'), ]; } private function normalize(?string $zipCode): ?string { $zipCode = preg_replace('/\D/', '', $zipCode ?? ''); return strlen($zipCode) === 8 ? $zipCode : null; } private function persistProviderCoordinates(string $zipCode, array $coordinates): void { $formattedZipCode = substr($zipCode, 0, 5).'-'.substr($zipCode, 5); Address::query() ->where('source', 'provider') ->whereIn('zip_code', [$zipCode, $formattedZipCode]) ->where(function ($query) { $query->whereNull('latitude')->orWhereNull('longitude'); }) ->update([ 'latitude' => $coordinates['latitude'], 'longitude' => $coordinates['longitude'], ]); } private function remember(string $zipCode, ?array $coordinates): void { $this->resolvedZipCodes[$zipCode] = $coordinates; if ($coordinates === null) { return; } Cache::put( "zip-code-coordinates:{$zipCode}", [ 'found' => $coordinates !== null, 'coordinates' => $coordinates, ], now()->addSeconds( (int) config('services.google_geocoding.cache_ttl', 2592000) ) ); $this->persistProviderCoordinates($zipCode, $coordinates); } private function requestCoordinates(string $zipCode): ?array { $startedAt = microtime(true); try { Log::channel('google_geocoding')->info('ZipCodeCoordinates calling Google Geocoding', [ 'zip_code' => $zipCode, 'base_url' => rtrim((string) config('services.google_geocoding.base_url'), '/'), 'connect_timeout' => (int) config('services.google_geocoding.connect_timeout', 2), 'timeout' => (int) config('services.google_geocoding.timeout', 5), 'ip_resolve' => 'ipv4', ]); $baseUrl = rtrim((string) config('services.google_geocoding.base_url'), '/'); $response = Http::acceptJson() ->withOptions($this->curlForceIpv4Options()) ->connectTimeout((int) config('services.google_geocoding.connect_timeout', 2)) ->timeout((int) config('services.google_geocoding.timeout', 5)) ->get("{$baseUrl}/maps/api/geocode/json", $this->googleQuery($zipCode)); return $this->coordinatesFromResponse($response); } catch (Throwable $exception) { Log::channel('google_geocoding')->warning('ZipCodeCoordinates Google Geocoding request failed', [ 'zip_code' => $zipCode, 'elapsed_ms' => round((microtime(true) - $startedAt) * 1000, 2), 'error' => $exception->getMessage(), ]); return null; } } private function restoreFromCache(string $zipCode): bool { $cacheKey = "zip-code-coordinates:{$zipCode}"; $cached = Cache::get($cacheKey); if (! is_array($cached) || ! array_key_exists('found', $cached)) { return false; } if (! $cached['found']) { Cache::forget($cacheKey); return false; } $this->resolvedZipCodes[$zipCode] = $cached['found'] ? $cached['coordinates'] : null; return true; } }