-
Notifications
You must be signed in to change notification settings - Fork 0
Simulation
Rumen Damyanov edited this page Jul 31, 2025
·
1 revision
Complete guide to testing geolocation functionality in local development environments.
- Why Simulation is Needed
- Basic Simulation
- Advanced Simulation Options
- GeolocationSimulator Class
- Testing Different Scenarios
- Framework Integration
- Debugging and Validation
In local development, Cloudflare headers are not available, making geolocation testing impossible. The simulation feature provides:
- Real-world testing: Test how your application behaves for users from different countries
- Language detection: Verify language negotiation for various regions
- Client simulation: Test with different browsers and devices
- Edge case testing: Validate error handling and fallback mechanisms
<?php
require_once 'vendor/autoload.php';
use Rumenx\Geolocation\Geolocation;
// Simulate a user from Germany
$geo = Geolocation::simulate('DE');
echo "Simulated Country: " . $geo->getCountryCode() . "\n";
echo "Detected Language: " . $geo->getLanguageForCountry('DE') . "\n";
echo "Is Development: " . ($geo->isLocalDevelopment() ? 'Yes' : 'No') . "\n";
/* Output:
Simulated Country: DE
Detected Language: de
Is Development: Yes
*/$testCountries = ['US', 'CA', 'GB', 'DE', 'FR', 'JP', 'BR'];
foreach ($testCountries as $country) {
$geo = Geolocation::simulate($country);
echo sprintf(
"Country: %s | Language: %s | IP: %s\n",
$geo->getCountryCode(),
$geo->getLanguageForCountry($country),
$geo->getIp()
);
}
/* Output:
Country: US | Language: en | IP: 192.168.1.100
Country: CA | Language: en | IP: 192.168.1.101
Country: GB | Language: en | IP: 192.168.1.102
Country: DE | Language: de | IP: 192.168.1.103
Country: FR | Language: fr | IP: 192.168.1.104
Country: JP | Language: ja | IP: 192.168.1.105
Country: BR | Language: pt | IP: 192.168.1.106
*/$customMapping = [
'US' => ['en'],
'CA' => ['en', 'fr'],
'CH' => ['de', 'fr', 'it'], // Switzerland: multiple languages
'BE' => ['fr', 'nl'], // Belgium: French and Dutch
'LU' => ['fr', 'de'], // Luxembourg: French and German
];
$geo = Geolocation::simulate('CH', $customMapping);
// Test language preferences
$availableLanguages = ['en', 'fr', 'de'];
$selectedLanguage = $geo->getLanguageForCountry('CH', $availableLanguages);
echo "Switzerland visitor gets language: {$selectedLanguage}";
// Output: "Switzerland visitor gets language: de" (first available match)// Different cookie names for different purposes
$mainSiteGeo = Geolocation::simulate('FR', [], 'main_language');
$adminGeo = Geolocation::simulate('FR', [], 'admin_language');
// Test language cookie detection
if ($mainSiteGeo->shouldSetLanguage()) {
echo "Set main site language cookie\n";
}
if ($adminGeo->shouldSetLanguage()) {
echo "Set admin language cookie\n";
}$customOptions = [
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'languages' => ['de-DE', 'de', 'en'],
'ip_range' => '192.168.100.',
'resolution' => ['width' => 1920, 'height' => 1080],
'device' => 'desktop'
];
$geo = Geolocation::simulate('DE', [], 'lang', $customOptions);
$info = $geo->getGeoInfo();
print_r($info);
/* Output:
Array (
[country_code] => DE
[ip] => 192.168.100.103
[preferred_language] => de-DE
[all_languages] => Array (
[0] => de-DE
[1] => de
[2] => en
)
[browser] => Array (
[browser] => Chrome
[version] => 91.0
)
[os] => Windows 10
[device] => desktop
[resolution] => Array (
[width] => 1920
[height] => 1080
)
)
*/use Rumenx\Geolocation\GeolocationSimulator;
$simulator = new GeolocationSimulator();
// Get realistic data for a country
$germanData = $simulator->getRealisticDataForCountry('DE');
print_r($germanData);
/* Output:
Array (
[HTTP_CF_IPCOUNTRY] => DE
[HTTP_ACCEPT_LANGUAGE] => de-DE,de;q=0.9,en;q=0.8
[HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
[REMOTE_ADDR] => 192.168.1.103
)
*/$simulator = new GeolocationSimulator();
$countries = $simulator->getAvailableCountries();
echo "Supported countries for simulation:\n";
foreach ($countries as $code => $data) {
echo "- {$code}: {$data['name']} (Language: {$data['language']})\n";
}
/* Output:
Supported countries for simulation:
- US: United States (Language: en-US)
- CA: Canada (Language: en-CA)
- GB: United Kingdom (Language: en-GB)
- DE: Germany (Language: de-DE)
- FR: France (Language: fr-FR)
- ES: Spain (Language: es-ES)
- JP: Japan (Language: ja-JP)
- BR: Brazil (Language: pt-BR)
*/$simulator = new GeolocationSimulator();
// Create custom simulation for testing
$customServer = $simulator->createCustomSimulation([
'country' => 'NL', // Netherlands - not in default list
'language' => 'nl-NL',
'languages' => ['nl-NL', 'nl', 'en'],
'user_agent' => 'Custom Mobile Browser/1.0',
'ip' => '10.0.0.50'
]);
$geo = new Geolocation($customServer);
echo "Custom simulation: " . $geo->getCountryCode();
// Output: "Custom simulation: NL"function testMultiLanguageSupport() {
$countryLanguageMapping = [
'US' => ['en'],
'CA' => ['en', 'fr'],
'CH' => ['de', 'fr', 'it'],
'BE' => ['fr', 'nl'],
];
$availableLanguages = ['en', 'fr', 'de'];
$testCases = [
'US' => 'en', // Should get English
'CA' => 'en', // Should get English (first match)
'CH' => 'de', // Should get German (first available)
'BE' => 'fr', // Should get French
];
foreach ($testCases as $country => $expectedLanguage) {
$geo = Geolocation::simulate($country, $countryLanguageMapping);
$detectedLanguage = $geo->getLanguageForCountry($country, $availableLanguages);
$status = $detectedLanguage === $expectedLanguage ? '✅' : '❌';
echo "{$status} {$country}: Expected {$expectedLanguage}, Got {$detectedLanguage}\n";
}
}
testMultiLanguageSupport();
/* Output:
✅ US: Expected en, Got en
✅ CA: Expected en, Got en
✅ CH: Expected de, Got de
✅ BE: Expected fr, Got fr
*/function testCookieBehavior() {
echo "Testing cookie behavior...\n\n";
// Test 1: New visitor (no cookie)
$_COOKIE = []; // Clear cookies
$geo = Geolocation::simulate('DE', [], 'test_lang');
echo "New German visitor:\n";
echo "- Should set language: " . ($geo->shouldSetLanguage() ? 'Yes' : 'No') . "\n";
echo "- Detected language: " . $geo->getLanguageForCountry('DE') . "\n\n";
// Test 2: Returning visitor (with cookie)
$_COOKIE['test_lang'] = 'en'; // Simulate existing cookie
$geo = Geolocation::simulate('DE', [], 'test_lang');
echo "Returning German visitor (has English cookie):\n";
echo "- Should set language: " . ($geo->shouldSetLanguage() ? 'Yes' : 'No') . "\n";
echo "- Cookie value: " . ($_COOKIE['test_lang'] ?? 'None') . "\n\n";
// Test 3: Different cookie name
$geo = Geolocation::simulate('DE', [], 'different_cookie');
echo "Same visitor, different cookie name:\n";
echo "- Should set language: " . ($geo->shouldSetLanguage() ? 'Yes' : 'No') . "\n";
}
testCookieBehavior();
/* Output:
Testing cookie behavior...
New German visitor:
- Should set language: Yes
- Detected language: de
Returning German visitor (has English cookie):
- Should set language: No
- Cookie value: en
Same visitor, different cookie name:
- Should set language: Yes
*/function testClientDetection() {
$testCases = [
[
'name' => 'Desktop Chrome',
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'expected_browser' => 'Chrome',
'expected_device' => 'desktop'
],
[
'name' => 'Mobile Safari',
'user_agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
'expected_browser' => 'Safari',
'expected_device' => 'mobile'
],
[
'name' => 'Firefox Desktop',
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
'expected_browser' => 'Firefox',
'expected_device' => 'desktop'
]
];
foreach ($testCases as $case) {
$options = ['user_agent' => $case['user_agent']];
$geo = Geolocation::simulate('US', [], 'lang', $options);
$browser = $geo->getBrowser();
$device = $geo->getDeviceType();
echo "Testing {$case['name']}:\n";
echo "- Browser: {$browser['browser']} (Expected: {$case['expected_browser']})\n";
echo "- Device: {$device} (Expected: {$case['expected_device']})\n\n";
}
}
testClientDetection();// In Laravel feature tests
use Rumenx\Geolocation\Geolocation;
class GeolocationFeatureTest extends TestCase
{
public function test_german_visitor_gets_german_content()
{
// Simulate German visitor
$geo = Geolocation::simulate('DE');
// Mock the geolocation service
$this->app->instance('geolocation', $geo);
$response = $this->get('/');
$response->assertSee('Willkommen'); // German welcome message
$response->assertViewHas('language', 'de');
}
public function test_api_returns_correct_geolocation()
{
$geo = Geolocation::simulate('FR');
$this->app->instance('geolocation', $geo);
$response = $this->getJson('/api/geolocation');
$response->assertJson([
'country_code' => 'FR',
'language' => 'fr'
]);
}
}// In Symfony functional tests
use Rumenx\Geolocation\Geolocation;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class GeolocationControllerTest extends WebTestCase
{
public function testGermanVisitorRedirect()
{
$client = static::createClient();
// Simulate server environment for German visitor
$geo = Geolocation::simulate('DE');
$serverData = $geo->getGeoInfo();
// Set server variables to simulate Cloudflare headers
$client->getContainer()->get('request_stack')
->getCurrentRequest()
->server->add([
'HTTP_CF_IPCOUNTRY' => 'DE'
]);
$crawler = $client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('html', 'Willkommen');
}
}function checkDevelopmentMode() {
$geo = new Geolocation();
echo "Environment Analysis:\n";
echo "- Is local development: " . ($geo->isLocalDevelopment() ? 'Yes' : 'No') . "\n";
echo "- Current IP: " . $geo->getIp() . "\n";
echo "- Country header: " . ($_SERVER['HTTP_CF_IPCOUNTRY'] ?? 'Not set') . "\n";
if ($geo->isLocalDevelopment()) {
echo "\n🔧 Development Mode Active\n";
echo "- Use Geolocation::simulate() for testing\n";
echo "- Cloudflare headers not available\n";
echo "- Simulation features enabled\n";
} else {
echo "\n🌍 Production Mode\n";
echo "- Using real Cloudflare data\n";
echo "- Simulation not needed\n";
}
}
checkDevelopmentMode();function validateSimulation($country, $expectedLanguage = null) {
echo "Validating simulation for {$country}...\n";
$geo = Geolocation::simulate($country);
$info = $geo->getGeoInfo();
$checks = [
'Country Code' => $info['country_code'] === $country,
'Has IP' => !empty($info['ip']),
'Has Language' => !empty($info['preferred_language']),
'Is Development' => $geo->isLocalDevelopment(),
];
if ($expectedLanguage) {
$checks['Correct Language'] = $info['preferred_language'] === $expectedLanguage;
}
foreach ($checks as $check => $passed) {
$status = $passed ? '✅' : '❌';
echo " {$status} {$check}\n";
}
echo "\nGenerated Data:\n";
foreach ($info as $key => $value) {
if (is_array($value)) {
echo " {$key}: " . json_encode($value) . "\n";
} else {
echo " {$key}: {$value}\n";
}
}
echo "\n" . str_repeat('-', 50) . "\n";
}
// Test multiple countries
validateSimulation('US', 'en-US');
validateSimulation('DE', 'de-DE');
validateSimulation('FR', 'fr-FR');function benchmarkSimulation() {
$countries = ['US', 'CA', 'GB', 'DE', 'FR', 'ES', 'JP', 'BR'];
$iterations = 1000;
echo "Benchmarking simulation performance...\n";
echo "Countries: " . count($countries) . "\n";
echo "Iterations per country: {$iterations}\n\n";
foreach ($countries as $country) {
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$geo = Geolocation::simulate($country);
$geo->getCountryCode();
$geo->getLanguageForCountry($country);
}
$duration = microtime(true) - $start;
$avgTime = ($duration / $iterations) * 1000; // Convert to milliseconds
echo "{$country}: {$avgTime:.2f}ms average per simulation\n";
}
}
benchmarkSimulation();
/* Sample Output:
Benchmarking simulation performance...
Countries: 8
Iterations per country: 1000
US: 0.12ms average per simulation
CA: 0.11ms average per simulation
GB: 0.13ms average per simulation
DE: 0.12ms average per simulation
FR: 0.11ms average per simulation
ES: 0.12ms average per simulation
JP: 0.13ms average per simulation
BR: 0.12ms average per simulation
*/function createEnvironmentAwareGeolocation($forceCountry = null) {
// Check environment
$isProduction = $_ENV['APP_ENV'] === 'production' ?? false;
$isDevelopment = $_ENV['APP_ENV'] === 'development' ?? true;
if ($isDevelopment && $forceCountry) {
// Development: use simulation
return Geolocation::simulate($forceCountry);
} elseif ($isDevelopment) {
// Development: check for simulation parameter
$simulateCountry = $_GET['simulate_country'] ?? $_ENV['SIMULATE_COUNTRY'] ?? null;
if ($simulateCountry) {
return Geolocation::simulate($simulateCountry);
}
}
// Production or no simulation needed
return new Geolocation();
}
// Usage
$geo = createEnvironmentAwareGeolocation($_GET['test_country'] ?? null);
echo "Country: " . $geo->getCountryCode();- 🌍 Language Negotiation - Advanced language handling
- 🔧 Configuration - Advanced configuration options
- 📊 Analytics Integration - Track geolocation data
- 🐛 Troubleshooting - Common issues and solutions
Previous: Laravel Integration | Next: Language Negotiation