-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
Rumen Damyanov edited this page Jul 31, 2025
·
3 revisions
Advanced configuration options and customization patterns for the php-geolocation package.
- Basic Configuration
- Country-Language Mapping
- Cookie Configuration
- Environment-Based Configuration
- Framework-Specific Configuration
- Advanced Patterns
- Performance Optimization
- Security Considerations
use Rumenx\Geolocation\Geolocation;
// Basic setup with default options
$geo = new Geolocation();
// With custom server data
$geo = new Geolocation($_SERVER);
// With language mapping
$countryMapping = [
'US' => ['en'],
'DE' => ['de'],
'FR' => ['fr']
];
$geo = new Geolocation($_SERVER, $countryMapping);
// With custom cookie name
$geo = new Geolocation($_SERVER, $countryMapping, 'user_lang');class GeolocationConfig
{
private array $countryToLanguage;
private string $cookieName;
private array $availableLanguages;
private string $defaultLanguage;
private array $options;
public function __construct() {
$this->setDefaults();
}
private function setDefaults(): void {
$this->countryToLanguage = [
'US' => ['en'], 'CA' => ['en', 'fr'], 'GB' => ['en'],
'DE' => ['de'], 'AT' => ['de'], 'CH' => ['de', 'fr', 'it'],
'FR' => ['fr'], 'BE' => ['fr', 'nl'], 'LU' => ['fr', 'de'],
'ES' => ['es'], 'MX' => ['es'], 'AR' => ['es'],
'BR' => ['pt'], 'PT' => ['pt'],
'IT' => ['it'], 'NL' => ['nl'],
'RU' => ['ru'], 'JP' => ['ja'], 'CN' => ['zh']
];
$this->cookieName = 'app_language';
$this->availableLanguages = ['en', 'fr', 'de', 'es'];
$this->defaultLanguage = 'en';
$this->options = [
'enable_simulation' => true,
'cache_duration' => 3600,
'fallback_country' => 'US',
'strict_mode' => false
];
}
public function setCountryMapping(array $mapping): self {
$this->countryToLanguage = $mapping;
return $this;
}
public function addCountryMapping(string $country, array $languages): self {
$this->countryToLanguage[$country] = $languages;
return $this;
}
public function setCookieName(string $name): self {
$this->cookieName = $name;
return $this;
}
public function setAvailableLanguages(array $languages): self {
$this->availableLanguages = $languages;
return $this;
}
public function setDefaultLanguage(string $language): self {
$this->defaultLanguage = $language;
return $this;
}
public function setOption(string $key, $value): self {
$this->options[$key] = $value;
return $this;
}
public function createGeolocation(array $server = []): Geolocation {
$server = $server ?: $_SERVER;
return new Geolocation($server, $this->countryToLanguage, $this->cookieName);
}
public function getCountryMapping(): array {
return $this->countryToLanguage;
}
public function getCookieName(): string {
return $this->cookieName;
}
public function getAvailableLanguages(): array {
return $this->availableLanguages;
}
public function getDefaultLanguage(): string {
return $this->defaultLanguage;
}
public function getOption(string $key, $default = null) {
return $this->options[$key] ?? $default;
}
}
// Usage
$config = new GeolocationConfig();
$config->setAvailableLanguages(['en', 'fr', 'de', 'es', 'pt'])
->setDefaultLanguage('en')
->setCookieName('site_language')
->addCountryMapping('BR', ['pt'])
->setOption('enable_simulation', $_ENV['APP_DEBUG'] ?? false);
$geo = $config->createGeolocation();function getComprehensiveCountryMapping(): array {
return [
// North America
'US' => ['en'],
'CA' => ['en', 'fr'],
'MX' => ['es'],
// South America
'BR' => ['pt'],
'AR' => ['es'],
'CL' => ['es'],
'CO' => ['es'],
'PE' => ['es'],
'VE' => ['es'],
'UY' => ['es'],
'PY' => ['es', 'gn'],
'BO' => ['es', 'qu', 'ay'],
'EC' => ['es'],
'GY' => ['en'],
'SR' => ['nl'],
'GF' => ['fr'],
// Europe - Western
'GB' => ['en'],
'IE' => ['en', 'ga'],
'FR' => ['fr'],
'DE' => ['de'],
'AT' => ['de'],
'CH' => ['de', 'fr', 'it', 'rm'],
'LI' => ['de'],
'LU' => ['fr', 'de', 'lb'],
'BE' => ['fr', 'nl', 'de'],
'NL' => ['nl'],
'ES' => ['es', 'ca', 'gl', 'eu'],
'PT' => ['pt'],
'IT' => ['it'],
'SM' => ['it'],
'VA' => ['it', 'la'],
'MT' => ['mt', 'en'],
'MC' => ['fr'],
'AD' => ['ca'],
// Europe - Northern
'DK' => ['da'],
'SE' => ['sv'],
'NO' => ['no', 'nb', 'nn'],
'FI' => ['fi', 'sv'],
'IS' => ['is'],
// Europe - Eastern
'PL' => ['pl'],
'CZ' => ['cs'],
'SK' => ['sk'],
'HU' => ['hu'],
'SI' => ['sl'],
'HR' => ['hr'],
'BA' => ['bs', 'hr', 'sr'],
'ME' => ['sr', 'bs', 'hr'],
'RS' => ['sr'],
'MK' => ['mk'],
'AL' => ['sq'],
'BG' => ['bg'],
'RO' => ['ro'],
'MD' => ['ro', 'ru'],
'UA' => ['uk', 'ru'],
'BY' => ['be', 'ru'],
'LT' => ['lt'],
'LV' => ['lv'],
'EE' => ['et'],
'RU' => ['ru'],
// Asia - East
'CN' => ['zh'],
'TW' => ['zh'],
'HK' => ['zh', 'en'],
'MO' => ['zh', 'pt'],
'JP' => ['ja'],
'KR' => ['ko'],
'KP' => ['ko'],
'MN' => ['mn'],
// Asia - Southeast
'TH' => ['th'],
'VN' => ['vi'],
'LA' => ['lo'],
'KH' => ['km'],
'MM' => ['my'],
'MY' => ['ms', 'en', 'zh', 'ta'],
'SG' => ['en', 'zh', 'ms', 'ta'],
'BN' => ['ms'],
'ID' => ['id'],
'TL' => ['pt', 'tet'],
'PH' => ['en', 'tl'],
// Asia - South
'IN' => ['hi', 'en'],
'PK' => ['ur', 'en'],
'BD' => ['bn'],
'LK' => ['si', 'ta'],
'MV' => ['dv'],
'NP' => ['ne'],
'BT' => ['dz'],
'AF' => ['fa', 'ps'],
// Asia - Central
'KZ' => ['kk', 'ru'],
'UZ' => ['uz', 'ru'],
'TM' => ['tk', 'ru'],
'KG' => ['ky', 'ru'],
'TJ' => ['tg', 'ru'],
// Asia - Western (Middle East)
'TR' => ['tr'],
'IR' => ['fa'],
'IQ' => ['ar', 'ku'],
'SY' => ['ar'],
'LB' => ['ar', 'fr'],
'JO' => ['ar'],
'IL' => ['he', 'ar'],
'PS' => ['ar'],
'SA' => ['ar'],
'YE' => ['ar'],
'OM' => ['ar'],
'AE' => ['ar'],
'QA' => ['ar'],
'BH' => ['ar'],
'KW' => ['ar'],
'GE' => ['ka'],
'AM' => ['hy'],
'AZ' => ['az'],
'CY' => ['el', 'tr'],
// Africa - North
'EG' => ['ar'],
'LY' => ['ar'],
'TN' => ['ar', 'fr'],
'DZ' => ['ar', 'fr'],
'MA' => ['ar', 'fr'],
'SD' => ['ar'],
'SS' => ['en'],
// Africa - West
'NG' => ['en'],
'GH' => ['en'],
'CI' => ['fr'],
'SN' => ['fr'],
'ML' => ['fr'],
'BF' => ['fr'],
'NE' => ['fr'],
'GN' => ['fr'],
'SL' => ['en'],
'LR' => ['en'],
'GM' => ['en'],
'GW' => ['pt'],
'CV' => ['pt'],
// Africa - East
'ET' => ['am'],
'KE' => ['sw', 'en'],
'UG' => ['en'],
'TZ' => ['sw', 'en'],
'RW' => ['rw', 'en', 'fr'],
'BI' => ['rn', 'fr'],
'SO' => ['so', 'ar'],
'DJ' => ['fr', 'ar'],
'ER' => ['ti', 'ar'],
// Africa - Central
'CD' => ['fr'],
'CG' => ['fr'],
'CF' => ['fr'],
'CM' => ['fr', 'en'],
'TD' => ['fr', 'ar'],
'GA' => ['fr'],
'GQ' => ['es', 'fr'],
'ST' => ['pt'],
'AO' => ['pt'],
// Africa - Southern
'ZA' => ['en', 'af', 'zu'],
'ZW' => ['en'],
'BW' => ['en'],
'NA' => ['en', 'af'],
'ZM' => ['en'],
'MW' => ['en'],
'MZ' => ['pt'],
'MG' => ['mg', 'fr'],
'MU' => ['en', 'fr'],
'SC' => ['en', 'fr'],
'KM' => ['ar', 'fr'],
'LS' => ['en'],
'SZ' => ['en'],
// Oceania
'AU' => ['en'],
'NZ' => ['en', 'mi'],
'PG' => ['en', 'tpi'],
'FJ' => ['en', 'fj'],
'VU' => ['bi', 'en', 'fr'],
'SB' => ['en'],
'NC' => ['fr'],
'PF' => ['fr'],
'WS' => ['sm', 'en'],
'TO' => ['to', 'en'],
'KI' => ['en'],
'TV' => ['en'],
'NR' => ['en'],
'PW' => ['en'],
'FM' => ['en'],
'MH' => ['en'],
];
}class RegionalMapping
{
private array $regions;
public function __construct() {
$this->regions = [
'EU' => [
'countries' => ['DE', 'FR', 'ES', 'IT', 'NL', 'BE', 'AT', 'PL'],
'languages' => ['en', 'de', 'fr', 'es', 'it'],
'default' => 'en'
],
'LATAM' => [
'countries' => ['MX', 'AR', 'CL', 'CO', 'PE', 'VE'],
'languages' => ['es', 'pt', 'en'],
'default' => 'es'
],
'ASIA' => [
'countries' => ['JP', 'KR', 'CN', 'TH', 'VN', 'ID'],
'languages' => ['en', 'ja', 'ko', 'zh'],
'default' => 'en'
],
'MENA' => [
'countries' => ['SA', 'AE', 'EG', 'MA', 'TR'],
'languages' => ['ar', 'en', 'tr'],
'default' => 'en'
]
];
}
public function getRegionForCountry(string $country): ?string {
foreach ($this->regions as $region => $data) {
if (in_array($country, $data['countries'])) {
return $region;
}
}
return null;
}
public function getLanguagesForRegion(string $region): array {
return $this->regions[$region]['languages'] ?? [];
}
public function getDefaultLanguageForRegion(string $region): string {
return $this->regions[$region]['default'] ?? 'en';
}
public function createMappingForRegions(array $regions): array {
$mapping = [];
$comprehensive = getComprehensiveCountryMapping();
foreach ($regions as $region) {
if (!isset($this->regions[$region])) continue;
$regionData = $this->regions[$region];
foreach ($regionData['countries'] as $country) {
// Use regional languages if country not in comprehensive mapping
$mapping[$country] = $comprehensive[$country] ?? $regionData['languages'];
}
}
return $mapping;
}
}
// Usage
$regionalMapping = new RegionalMapping();
// Create mapping for specific regions
$euMapping = $regionalMapping->createMappingForRegions(['EU']);
$globalMapping = $regionalMapping->createMappingForRegions(['EU', 'LATAM', 'ASIA']);
$config = new GeolocationConfig();
$config->setCountryMapping($globalMapping);class CookieConfig
{
private string $name;
private int $lifetime;
private string $path;
private ?string $domain;
private bool $secure;
private bool $httpOnly;
private string $sameSite;
public function __construct(array $options = []) {
$this->name = $options['name'] ?? 'app_language';
$this->lifetime = $options['lifetime'] ?? 2592000; // 30 days
$this->path = $options['path'] ?? '/';
$this->domain = $options['domain'] ?? null;
$this->secure = $options['secure'] ?? false;
$this->httpOnly = $options['httpOnly'] ?? true;
$this->sameSite = $options['sameSite'] ?? 'Lax';
}
public function setCookie(string $value): bool {
return setcookie(
$this->name,
$value,
[
'expires' => time() + $this->lifetime,
'path' => $this->path,
'domain' => $this->domain,
'secure' => $this->secure,
'httponly' => $this->httpOnly,
'samesite' => $this->sameSite
]
);
}
public function getCookie(): ?string {
return $_COOKIE[$this->name] ?? null;
}
public function deleteCookie(): bool {
return setcookie(
$this->name,
'',
[
'expires' => time() - 3600,
'path' => $this->path,
'domain' => $this->domain,
'secure' => $this->secure,
'httponly' => $this->httpOnly,
'samesite' => $this->sameSite
]
);
}
public function getName(): string {
return $this->name;
}
public static function create(string $name, array $options = []): self {
return new self(array_merge(['name' => $name], $options));
}
}
// Usage
$cookieConfig = CookieConfig::create('user_language', [
'lifetime' => 86400 * 90, // 90 days
'secure' => $_SERVER['HTTPS'] ?? false,
'domain' => '.example.com' // Set for subdomain sharing
]);
// Multiple cookie configurations
$mainCookie = CookieConfig::create('main_lang');
$adminCookie = CookieConfig::create('admin_lang', ['path' => '/admin']);
$apiCookie = CookieConfig::create('api_lang', ['httpOnly' => false]); // Accessible via JSclass EncryptedCookieConfig extends CookieConfig
{
private string $encryptionKey;
public function __construct(array $options = []) {
parent::__construct($options);
$this->encryptionKey = $options['encryption_key'] ??
$_ENV['APP_KEY'] ??
'default-key-change-in-production';
}
public function setCookie(string $value): bool {
$encrypted = $this->encrypt($value);
return parent::setCookie($encrypted);
}
public function getCookie(): ?string {
$encrypted = parent::getCookie();
return $encrypted ? $this->decrypt($encrypted) : null;
}
private function encrypt(string $data): string {
$iv = random_bytes(16);
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $this->encryptionKey, 0, $iv);
return base64_encode($iv . $encrypted);
}
private function decrypt(string $data): ?string {
$data = base64_decode($data);
if ($data === false || strlen($data) < 16) {
return null;
}
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $this->encryptionKey, 0, $iv);
return $decrypted !== false ? $decrypted : null;
}
}
// Usage
$encryptedCookie = new EncryptedCookieConfig([
'name' => 'secure_language',
'encryption_key' => $_ENV['COOKIE_ENCRYPTION_KEY']
]);class EnvironmentConfig
{
private string $environment;
private array $configs;
public function __construct() {
$this->environment = $this->detectEnvironment();
$this->configs = $this->loadConfigs();
}
private function detectEnvironment(): string {
// Check environment variable
if ($env = $_ENV['APP_ENV'] ?? $_ENV['ENVIRONMENT'] ?? null) {
return $env;
}
// Check hostname patterns
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
if (preg_match('/localhost|127\.0\.0\.1|\.local$|\.dev$/', $host)) {
return 'development';
} elseif (preg_match('/staging|test/', $host)) {
return 'staging';
} else {
return 'production';
}
}
private function loadConfigs(): array {
return [
'development' => [
'enable_simulation' => true,
'debug_headers' => true,
'cache_duration' => 0,
'cookie_secure' => false,
'available_languages' => ['en', 'fr', 'de', 'es', 'pt'],
'fallback_country' => 'US',
'strict_validation' => false
],
'staging' => [
'enable_simulation' => true,
'debug_headers' => true,
'cache_duration' => 300, // 5 minutes
'cookie_secure' => true,
'available_languages' => ['en', 'fr', 'de', 'es'],
'fallback_country' => 'US',
'strict_validation' => true
],
'production' => [
'enable_simulation' => false,
'debug_headers' => false,
'cache_duration' => 3600, // 1 hour
'cookie_secure' => true,
'available_languages' => ['en', 'fr', 'de'],
'fallback_country' => 'US',
'strict_validation' => true
]
];
}
public function get(string $key, $default = null) {
return $this->configs[$this->environment][$key] ?? $default;
}
public function getEnvironment(): string {
return $this->environment;
}
public function isProduction(): bool {
return $this->environment === 'production';
}
public function isDevelopment(): bool {
return $this->environment === 'development';
}
public function createGeolocationConfig(): GeolocationConfig {
$config = new GeolocationConfig();
$config->setAvailableLanguages($this->get('available_languages'))
->setOption('enable_simulation', $this->get('enable_simulation'))
->setOption('cache_duration', $this->get('cache_duration'))
->setOption('fallback_country', $this->get('fallback_country'));
return $config;
}
}
// Usage
$envConfig = new EnvironmentConfig();
$geoConfig = $envConfig->createGeolocationConfig();
if ($envConfig->isDevelopment()) {
// Development-specific settings
$geoConfig->addCountryMapping('TEST', ['en']);
}
$geo = $geoConfig->createGeolocation();class ConfigurationLoader
{
private array $config;
private string $configPath;
public function __construct(string $configPath = null) {
$this->configPath = $configPath ?: __DIR__ . '/config';
$this->config = $this->loadConfiguration();
}
private function loadConfiguration(): array {
$baseConfig = $this->loadFile('geolocation.php');
$envConfig = $this->loadFile('geolocation.' . $this->getEnvironment() . '.php');
return array_merge($baseConfig, $envConfig);
}
private function loadFile(string $filename): array {
$filepath = $this->configPath . '/' . $filename;
if (file_exists($filepath)) {
return include $filepath;
}
return [];
}
private function getEnvironment(): string {
return $_ENV['APP_ENV'] ?? 'production';
}
public function get(string $key, $default = null) {
return $this->getNestedValue($this->config, $key, $default);
}
private function getNestedValue(array $array, string $key, $default = null) {
if (strpos($key, '.') === false) {
return $array[$key] ?? $default;
}
$keys = explode('.', $key);
$value = $array;
foreach ($keys as $k) {
if (!is_array($value) || !isset($value[$k])) {
return $default;
}
$value = $value[$k];
}
return $value;
}
public function createGeolocation(): Geolocation {
$countryMapping = $this->get('country_to_language', []);
$cookieName = $this->get('cookie.name', 'language');
return new Geolocation($_SERVER, $countryMapping, $cookieName);
}
}
// Configuration files:
// config/geolocation.php (base configuration)
/*
<?php
return [
'country_to_language' => [
'US' => ['en'], 'CA' => ['en', 'fr'], 'GB' => ['en'],
'DE' => ['de'], 'FR' => ['fr'], 'ES' => ['es'],
],
'available_languages' => ['en', 'fr', 'de', 'es'],
'default_language' => 'en',
'cookie' => [
'name' => 'app_language',
'lifetime' => 2592000, // 30 days
'secure' => false,
'httponly' => true
],
'simulation' => [
'enabled' => false,
'default_country' => 'US'
],
'cache' => [
'enabled' => true,
'duration' => 3600
]
];
*/
// config/geolocation.development.php (development overrides)
/*
<?php
return [
'simulation' => [
'enabled' => true,
'default_country' => $_ENV['DEV_COUNTRY'] ?? 'US'
],
'cookie' => [
'secure' => false
],
'cache' => [
'duration' => 60 // Short cache in development
]
];
*/
// Usage
$configLoader = new ConfigurationLoader();
$geo = $configLoader->createGeolocation();
$enableSimulation = $configLoader->get('simulation.enabled');
$cacheEnabled = $configLoader->get('cache.enabled');// config/geolocation.php for Laravel
<?php
return [
/*
|--------------------------------------------------------------------------
| Country to Language Mapping
|--------------------------------------------------------------------------
*/
'country_to_language' => [
'US' => ['en'], 'CA' => ['en', 'fr'], 'GB' => ['en'],
'DE' => ['de'], 'AT' => ['de'], 'CH' => ['de', 'fr', 'it'],
'FR' => ['fr'], 'BE' => ['fr', 'nl'], 'ES' => ['es'],
'IT' => ['it'], 'NL' => ['nl'], 'PT' => ['pt'], 'BR' => ['pt'],
'RU' => ['ru'], 'JP' => ['ja'], 'CN' => ['zh'],
],
/*
|--------------------------------------------------------------------------
| Available Languages
|--------------------------------------------------------------------------
*/
'available_languages' => explode(',', env('GEOLOCATION_LANGUAGES', 'en,fr,de,es')),
/*
|--------------------------------------------------------------------------
| Default Settings
|--------------------------------------------------------------------------
*/
'default_language' => env('GEOLOCATION_DEFAULT_LANGUAGE', 'en'),
'cookie_name' => env('GEOLOCATION_COOKIE', 'app_language'),
'cookie_lifetime' => env('GEOLOCATION_COOKIE_LIFETIME', 2592000),
/*
|--------------------------------------------------------------------------
| Simulation Settings
|--------------------------------------------------------------------------
*/
'simulation' => [
'enabled' => env('GEOLOCATION_SIMULATION', env('APP_DEBUG', false)),
'default_country' => env('GEOLOCATION_SIMULATE_COUNTRY'),
'countries' => ['US', 'CA', 'GB', 'DE', 'FR', 'ES', 'JP', 'BR'],
],
/*
|--------------------------------------------------------------------------
| Cache Settings
|--------------------------------------------------------------------------
*/
'cache' => [
'enabled' => env('GEOLOCATION_CACHE', true),
'duration' => env('GEOLOCATION_CACHE_DURATION', 3600),
'key_prefix' => 'geo:',
],
/*
|--------------------------------------------------------------------------
| Security Settings
|--------------------------------------------------------------------------
*/
'security' => [
'encrypt_cookies' => env('GEOLOCATION_ENCRYPT_COOKIES', false),
'validate_headers' => env('GEOLOCATION_VALIDATE_HEADERS', true),
'rate_limit' => env('GEOLOCATION_RATE_LIMIT', 100), // per minute
],
];# config/packages/geolocation.yaml
geolocation:
country_to_language:
US: [en]
CA: [en, fr]
GB: [en]
DE: [de]
AT: [de]
CH: [de, fr, it]
FR: [fr]
BE: [fr, nl]
ES: [es]
IT: [it]
NL: [nl]
PT: [pt]
BR: [pt]
RU: [ru]
JP: [ja]
CN: [zh]
available_languages: ['en', 'fr', 'de', 'es']
default_language: en
cookie:
name: app_language
lifetime: 2592000 # 30 days
secure: '%kernel.environment% == "prod"'
httponly: true
samesite: Lax
simulation:
enabled: '%kernel.debug%'
default_country: '%env(GEOLOCATION_SIMULATE_COUNTRY)%'
cache:
enabled: true
duration: 3600
service: cache.appclass GeolocationConfigBuilder
{
private GeolocationConfig $config;
public function __construct() {
$this->config = new GeolocationConfig();
}
public function forRegion(string $region): self {
$regionalMapping = new RegionalMapping();
$mapping = $regionalMapping->createMappingForRegions([$region]);
$languages = $regionalMapping->getLanguagesForRegion($region);
$defaultLang = $regionalMapping->getDefaultLanguageForRegion($region);
$this->config->setCountryMapping($mapping)
->setAvailableLanguages($languages)
->setDefaultLanguage($defaultLang);
return $this;
}
public function forEnvironment(string $env): self {
$envConfig = new EnvironmentConfig();
switch ($env) {
case 'development':
$this->config->setOption('enable_simulation', true)
->setOption('debug_headers', true)
->setOption('cache_duration', 0);
break;
case 'production':
$this->config->setOption('enable_simulation', false)
->setOption('debug_headers', false)
->setOption('cache_duration', 3600);
break;
}
return $this;
}
public function withLanguages(array $languages): self {
$this->config->setAvailableLanguages($languages);
return $this;
}
public function withSecureCookies(bool $secure = true): self {
$this->config->setOption('secure_cookies', $secure);
return $this;
}
public function withCaching(int $duration = 3600): self {
$this->config->setOption('cache_enabled', true)
->setOption('cache_duration', $duration);
return $this;
}
public function withFallback(string $country, string $language): self {
$this->config->setOption('fallback_country', $country)
->setDefaultLanguage($language);
return $this;
}
public function build(): GeolocationConfig {
return $this->config;
}
}
// Usage examples
$config = (new GeolocationConfigBuilder())
->forRegion('EU')
->forEnvironment('production')
->withLanguages(['en', 'de', 'fr'])
->withSecureCookies()
->withCaching(7200)
->build();
$geo = $config->createGeolocation();
// Or for development
$devConfig = (new GeolocationConfigBuilder())
->forEnvironment('development')
->withLanguages(['en', 'fr', 'de', 'es', 'pt'])
->withFallback('US', 'en')
->build();class DynamicGeolocationConfig
{
private array $rules;
public function __construct() {
$this->rules = [];
}
public function addRule(callable $condition, array $config): self {
$this->rules[] = [
'condition' => $condition,
'config' => $config
];
return $this;
}
public function forUserAgent(string $pattern, array $config): self {
return $this->addRule(
fn() => preg_match($pattern, $_SERVER['HTTP_USER_AGENT'] ?? ''),
$config
);
}
public function forDomain(string $domain, array $config): self {
return $this->addRule(
fn() => strpos($_SERVER['HTTP_HOST'] ?? '', $domain) !== false,
$config
);
}
public function forCountry(string $country, array $config): self {
return $this->addRule(
function() use ($country) {
$geo = new Geolocation($_SERVER);
return $geo->getCountryCode() === $country;
},
$config
);
}
public function forTime(string $timezone, int $startHour, int $endHour, array $config): self {
return $this->addRule(
function() use ($timezone, $startHour, $endHour) {
$tz = new DateTimeZone($timezone);
$time = new DateTime('now', $tz);
$hour = (int)$time->format('H');
return $hour >= $startHour && $hour <= $endHour;
},
$config
);
}
public function resolve(): GeolocationConfig {
$config = new GeolocationConfig();
foreach ($this->rules as $rule) {
if (($rule['condition'])()) {
foreach ($rule['config'] as $key => $value) {
if (method_exists($config, 'set' . ucfirst($key))) {
$config->{'set' . ucfirst($key)}($value);
} else {
$config->setOption($key, $value);
}
}
}
}
return $config;
}
}
// Usage
$dynamicConfig = new DynamicGeolocationConfig();
$dynamicConfig
->forDomain('admin.example.com', [
'availableLanguages' => ['en'],
'cookieName' => 'admin_language'
])
->forUserAgent('/Mobile/', [
'availableLanguages' => ['en', 'es'], // Limited languages for mobile
'enable_simulation' => false
])
->forCountry('CN', [
'availableLanguages' => ['zh', 'en'],
'defaultLanguage' => 'zh'
])
->forTime('America/New_York', 9, 17, [
'cache_duration' => 1800 // Shorter cache during business hours
]);
$config = $dynamicConfig->resolve();
$geo = $config->createGeolocation();class CachedGeolocationConfig
{
private $cache;
private int $cacheDuration;
public function __construct($cache, int $duration = 3600) {
$this->cache = $cache;
$this->cacheDuration = $duration;
}
public function getCachedGeoInfo(string $cacheKey, callable $resolver): array {
// Try to get from cache first
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $cached;
}
// Resolve and cache
$geoInfo = $resolver();
$this->cache->set($cacheKey, $geoInfo, $this->cacheDuration);
return $geoInfo;
}
public function createCachedGeolocation(): Geolocation {
$cacheKey = $this->generateCacheKey();
return $this->getCachedGeoInfo($cacheKey, function() {
return new Geolocation($_SERVER);
});
}
private function generateCacheKey(): string {
$factors = [
$_SERVER['HTTP_CF_IPCOUNTRY'] ?? '',
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',
$_SERVER['HTTP_USER_AGENT'] ?? '',
$_SERVER['REMOTE_ADDR'] ?? ''
];
return 'geo:' . hash('crc32', implode('|', $factors));
}
public function warmCache(array $countries): void {
foreach ($countries as $country) {
$geo = Geolocation::simulate($country);
$cacheKey = 'geo:simulated:' . $country;
$this->cache->set($cacheKey, $geo->getGeoInfo(), $this->cacheDuration);
}
}
}
// Usage with different cache backends
// With APCu
class ApcuCache {
public function get(string $key) {
return apcu_fetch($key) ?: null;
}
public function set(string $key, $value, int $duration): void {
apcu_store($key, $value, $duration);
}
}
// With Redis
class RedisCache {
private $redis;
public function __construct($redis) {
$this->redis = $redis;
}
public function get(string $key) {
$value = $this->redis->get($key);
return $value ? unserialize($value) : null;
}
public function set(string $key, $value, int $duration): void {
$this->redis->setex($key, $duration, serialize($value));
}
}
// Usage
$cache = new ApcuCache(); // or new RedisCache($redis)
$cachedConfig = new CachedGeolocationConfig($cache, 1800);
// Warm cache for common countries
$cachedConfig->warmCache(['US', 'GB', 'DE', 'FR', 'ES', 'JP']);class SecureGeolocationConfig
{
private array $allowedCountries;
private array $allowedLanguages;
private bool $strictValidation;
public function __construct(array $allowedCountries = [], bool $strict = true) {
$this->allowedCountries = $allowedCountries;
$this->allowedLanguages = $this->getValidLanguageCodes();
$this->strictValidation = $strict;
}
public function validateAndCreateGeolocation(array $server): ?Geolocation {
if (!$this->validateServerData($server)) {
return null;
}
$cleanServer = $this->sanitizeServerData($server);
return new Geolocation($cleanServer);
}
private function validateServerData(array $server): bool {
// Validate country code
if (isset($server['HTTP_CF_IPCOUNTRY'])) {
if (!$this->isValidCountryCode($server['HTTP_CF_IPCOUNTRY'])) {
return false;
}
}
// Validate IP address
if (isset($server['REMOTE_ADDR'])) {
if (!filter_var($server['REMOTE_ADDR'], FILTER_VALIDATE_IP)) {
return false;
}
}
// Validate User-Agent length
if (isset($server['HTTP_USER_AGENT'])) {
if (strlen($server['HTTP_USER_AGENT']) > 1000) {
return false;
}
}
return true;
}
private function sanitizeServerData(array $server): array {
$clean = [];
// Sanitize country code
if (isset($server['HTTP_CF_IPCOUNTRY'])) {
$country = strtoupper(trim($server['HTTP_CF_IPCOUNTRY']));
if ($this->isValidCountryCode($country)) {
$clean['HTTP_CF_IPCOUNTRY'] = $country;
}
}
// Sanitize and validate IP
if (isset($server['REMOTE_ADDR'])) {
$ip = filter_var($server['REMOTE_ADDR'], FILTER_VALIDATE_IP);
if ($ip) {
$clean['REMOTE_ADDR'] = $ip;
}
}
// Sanitize User-Agent
if (isset($server['HTTP_USER_AGENT'])) {
$userAgent = strip_tags(trim($server['HTTP_USER_AGENT']));
$clean['HTTP_USER_AGENT'] = substr($userAgent, 0, 500);
}
// Sanitize Accept-Language
if (isset($server['HTTP_ACCEPT_LANGUAGE'])) {
$acceptLang = $this->sanitizeAcceptLanguage($server['HTTP_ACCEPT_LANGUAGE']);
if ($acceptLang) {
$clean['HTTP_ACCEPT_LANGUAGE'] = $acceptLang;
}
}
return $clean;
}
private function isValidCountryCode(string $code): bool {
if (!preg_match('/^[A-Z]{2}$/', $code)) {
return false;
}
if (!empty($this->allowedCountries)) {
return in_array($code, $this->allowedCountries);
}
return true;
}
private function sanitizeAcceptLanguage(string $acceptLanguage): ?string {
// Remove any suspicious characters
$clean = preg_replace('/[^a-zA-Z0-9\-,;=\.\s]/', '', $acceptLanguage);
// Validate format
if (!preg_match('/^[a-zA-Z\-,;=\.\s\d]+$/', $clean)) {
return null;
}
return substr($clean, 0, 200);
}
private function getValidLanguageCodes(): array {
return [
'en', 'fr', 'de', 'es', 'it', 'pt', 'ru', 'ja', 'zh', 'ko',
'ar', 'hi', 'th', 'vi', 'nl', 'sv', 'da', 'no', 'fi', 'pl',
'tr', 'he', 'id', 'ms', 'tl', 'sw', 'am', 'bn', 'gu', 'kn',
'ml', 'ta', 'te', 'ur', 'fa', 'ku', 'az', 'ka', 'hy', 'be',
'bg', 'hr', 'cs', 'et', 'lv', 'lt', 'hu', 'mt', 'ro', 'sk',
'sl', 'sq', 'sr', 'uk', 'mk', 'is', 'ga', 'cy', 'eu', 'ca',
'gl', 'lb', 'mi', 'sm', 'to', 'fj', 'mg', 'ny', 'sn', 'so',
'rw', 'lg', 'wo', 'ig', 'yo', 'ha', 'zu', 'af', 'st', 'tn',
'ts', 've', 'xh', 'ss', 'nr', 'nso', 'hy'
];
}
}
// Usage
$secureConfig = new SecureGeolocationConfig(
['US', 'CA', 'GB', 'DE', 'FR', 'ES'], // Allowed countries
true // Strict validation
);
$geo = $secureConfig->validateAndCreateGeolocation($_SERVER);
if (!$geo) {
// Handle validation failure
error_log('Geolocation validation failed for: ' . $_SERVER['REMOTE_ADDR']);
$geo = Geolocation::simulate('US'); // Safe fallback
}- 🔧 Troubleshooting - Common issues and solutions
- 🌍 Multi-language Websites - Complete internationalization examples
- 📊 Analytics Integration - Track geolocation and client data
- 🏗️ Pure PHP Integration - Framework-agnostic usage
Previous: Client Detection | Next: Troubleshooting