*/ private static $requestCache = []; /** @var string|null */ private static $lastSource = null; /** * @param string $ip * @return array{code:?string, source:string} */ public static function resolveWithMeta($ip) { $ip = ClientIpResolver::normalizeIp($ip); if ($ip === null) { self::$lastSource = 'invalid_ip'; return ['code' => null, 'source' => 'invalid_ip']; } if (array_key_exists($ip, self::$requestCache)) { self::$lastSource = 'request'; return ['code' => self::$requestCache[$ip], 'source' => 'request']; } $cached = GeoIpCountryCache::get($ip); if ($cached !== false) { self::$requestCache[$ip] = $cached; self::$lastSource = 'cache'; return ['code' => $cached, 'source' => 'cache']; } $code = self::lookupMmdb($ip); GeoIpCountryCache::set($ip, $code); self::$requestCache[$ip] = $code; self::$lastSource = 'mmdb'; return ['code' => $code, 'source' => 'mmdb']; } /** * @param string $ip * @return string|null ISO 3166-1 alpha-2 */ public static function resolve($ip) { $result = self::resolveWithMeta($ip); return $result['code']; } public static function lastSource() { return self::$lastSource ?? ''; } public static function isAvailable() { return self::getReader() !== null; } /** * @param string $ip * @return string|null */ private static function lookupMmdb($ip) { $reader = self::getReader(); if ($reader === null) { return null; } try { $record = $reader->country($ip); $isoCode = $record->country->isoCode ?? null; $name = $record->country->name ?? null; $code = CountryCodeNormalizer::toAlpha2($isoCode); if ($code === null && $name !== null && $name !== '') { $code = CountryCodeNormalizer::toAlpha2($name); } return $code; } catch (GeoIp2\Exception\AddressNotFoundException $e) { return null; } catch (Throwable $e) { GeoIpCountryCache::logError('mmdb lookup failed: ' . $e->getMessage()); return null; } } /** * @return GeoIp2\Database\Reader|null */ private static function getReader() { if (self::$reader !== null) { return self::$reader; } if (!class_exists('GeoIp2\\Database\\Reader')) { return null; } if (!defined('CLOAK_GEOIP_DB_PATH') || !is_file(CLOAK_GEOIP_DB_PATH)) { return null; } try { self::$reader = new GeoIp2\Database\Reader(CLOAK_GEOIP_DB_PATH); return self::$reader; } catch (Throwable $e) { GeoIpCountryCache::logError('Reader init failed: ' . $e->getMessage()); return null; } } }