Skip to content

Simulation

Rumen Damyanov edited this page Jul 31, 2025 · 1 revision

Local Development Simulation

Complete guide to testing geolocation functionality in local development environments.

Table of Contents

Why Simulation is Needed

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

Basic Simulation

Simple Country Simulation

<?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
*/

Testing Multiple Countries

$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
*/

Advanced Simulation Options

Custom Language Mapping

$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)

Custom Cookie Names

// 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";
}

Complete Custom Simulation

$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
    )
)
*/

GeolocationSimulator Class

Direct Usage

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
)
*/

Available Countries

$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)
*/

Custom Simulation Data

$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"

Testing Different Scenarios

Multi-language Website Testing

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
*/

Cookie Behavior Testing

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
*/

Browser and Device Testing

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();

Framework Integration

Laravel Testing

// 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'
        ]);
    }
}

Symfony Testing

// 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');
    }
}

Debugging and Validation

Development Detection

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();

Validation Helper

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');

Performance Testing

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
*/

Best Practices

Environment-Based Configuration

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();

Next Steps


Previous: Laravel Integration | Next: Language Negotiation

Clone this wiki locally