Skip to content

Analytics

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

Analytics Integration

Comprehensive guide for tracking geographic data, user behavior analytics, and business intelligence using php-geolocation with popular analytics platforms.

Table of Contents

Analytics Strategy

Geographic Analytics Framework

<?php
// src/GeographicAnalytics.php
namespace Rumenx\Geolocation;

use Psr\Log\LoggerInterface;
use GuzzleHttp\Client;

class GeographicAnalytics
{
    private Geolocation $geo;
    private LoggerInterface $logger;
    private Client $httpClient;
    private array $config;
    private array $trackingQueue = [];

    public function __construct(
        Geolocation $geo,
        LoggerInterface $logger,
        Client $httpClient,
        array $config = []
    ) {
        $this->geo = $geo;
        $this->logger = $logger;
        $this->httpClient = $httpClient;
        $this->config = array_merge($this->getDefaultConfig(), $config);
    }

    private function getDefaultConfig(): array
    {
        return [
            'google_analytics' => [
                'measurement_id' => '',
                'api_secret' => '',
                'enabled' => false
            ],
            'custom_analytics' => [
                'endpoint' => '',
                'api_key' => '',
                'enabled' => false
            ],
            'privacy' => [
                'anonymize_ip' => true,
                'respect_dnt' => true,
                'gdpr_compliant' => true
            ],
            'tracking' => [
                'page_views' => true,
                'events' => true,
                'conversions' => true,
                'user_timing' => true
            ],
            'batch_size' => 20,
            'flush_interval' => 300 // 5 minutes
        ];
    }

    public function trackPageView(array $pageData = []): void
    {
        if (!$this->config['tracking']['page_views']) {
            return;
        }

        $country = $this->geo->getCountryCode();
        $language = $this->geo->getLanguage();

        $data = array_merge([
            'event_type' => 'page_view',
            'timestamp' => time(),
            'page_url' => $_SERVER['REQUEST_URI'] ?? '/',
            'page_title' => $pageData['title'] ?? '',
            'referrer' => $_SERVER['HTTP_REFERER'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'country' => $country,
            'language' => $language,
            'session_id' => session_id(),
            'user_id' => $pageData['user_id'] ?? null,
            'custom_dimensions' => [
                'geo_country' => $country,
                'geo_language' => $language,
                'geo_region' => $this->geo->getRegion(),
                'geo_city' => $this->geo->getCity(),
                'device_type' => $this->getDeviceType(),
                'browser' => $this->getBrowser(),
                'os' => $this->getOperatingSystem()
            ]
        ], $pageData);

        $this->queueEvent($data);
    }

    public function trackEvent(string $eventName, array $parameters = []): void
    {
        if (!$this->config['tracking']['events']) {
            return;
        }

        $country = $this->geo->getCountryCode();
        $language = $this->geo->getLanguage();

        $data = [
            'event_type' => 'custom_event',
            'event_name' => $eventName,
            'timestamp' => time(),
            'country' => $country,
            'language' => $language,
            'session_id' => session_id(),
            'parameters' => array_merge($parameters, [
                'geo_country' => $country,
                'geo_language' => $language,
                'geo_region' => $this->geo->getRegion(),
                'geo_city' => $this->geo->getCity()
            ])
        ];

        $this->queueEvent($data);

        $this->logger->info('Analytics event tracked', [
            'event' => $eventName,
            'country' => $country,
            'parameters' => $parameters
        ]);
    }

    public function trackConversion(string $conversionType, float $value = 0.0, string $currency = 'USD', array $items = []): void
    {
        if (!$this->config['tracking']['conversions']) {
            return;
        }

        $country = $this->geo->getCountryCode();
        $language = $this->geo->getLanguage();

        $data = [
            'event_type' => 'conversion',
            'conversion_type' => $conversionType,
            'timestamp' => time(),
            'country' => $country,
            'language' => $language,
            'value' => $value,
            'currency' => $currency,
            'items' => $items,
            'session_id' => session_id(),
            'custom_dimensions' => [
                'geo_country' => $country,
                'geo_language' => $language,
                'conversion_geo_segment' => $this->getGeoSegment($country)
            ]
        ];

        $this->queueEvent($data);

        $this->logger->info('Conversion tracked', [
            'type' => $conversionType,
            'value' => $value,
            'currency' => $currency,
            'country' => $country
        ]);
    }

    public function trackUserTiming(string $category, string $variable, int $timeMs, string $label = ''): void
    {
        if (!$this->config['tracking']['user_timing']) {
            return;
        }

        $country = $this->geo->getCountryCode();

        $data = [
            'event_type' => 'user_timing',
            'timing_category' => $category,
            'timing_variable' => $variable,
            'timing_value' => $timeMs,
            'timing_label' => $label,
            'timestamp' => time(),
            'country' => $country,
            'session_id' => session_id()
        ];

        $this->queueEvent($data);
    }

    private function queueEvent(array $data): void
    {
        // Add client information
        $data['client_info'] = [
            'ip_address' => $this->getClientIP(),
            'screen_resolution' => $_COOKIE['screen_resolution'] ?? null,
            'viewport_size' => $_COOKIE['viewport_size'] ?? null,
            'timezone' => $_COOKIE['timezone'] ?? null
        ];

        // Anonymize data if required
        if ($this->config['privacy']['anonymize_ip']) {
            $data['client_info']['ip_address'] = $this->anonymizeIP($data['client_info']['ip_address']);
        }

        // Check Do Not Track
        if ($this->config['privacy']['respect_dnt'] && $this->isDNTEnabled()) {
            return;
        }

        $this->trackingQueue[] = $data;

        // Auto-flush if batch size reached
        if (count($this->trackingQueue) >= $this->config['batch_size']) {
            $this->flush();
        }
    }

    public function flush(): void
    {
        if (empty($this->trackingQueue)) {
            return;
        }

        // Send to Google Analytics 4
        if ($this->config['google_analytics']['enabled']) {
            $this->sendToGoogleAnalytics($this->trackingQueue);
        }

        // Send to custom analytics
        if ($this->config['custom_analytics']['enabled']) {
            $this->sendToCustomAnalytics($this->trackingQueue);
        }

        // Log events
        $this->logEvents($this->trackingQueue);

        // Clear queue
        $this->trackingQueue = [];
    }

    private function sendToGoogleAnalytics(array $events): void
    {
        $measurementId = $this->config['google_analytics']['measurement_id'];
        $apiSecret = $this->config['google_analytics']['api_secret'];

        if (!$measurementId || !$apiSecret) {
            return;
        }

        $clientId = $this->getClientId();
        $payload = [
            'client_id' => $clientId,
            'events' => []
        ];

        foreach ($events as $event) {
            $gaEvent = $this->convertToGoogleAnalyticsFormat($event);
            if ($gaEvent) {
                $payload['events'][] = $gaEvent;
            }
        }

        if (empty($payload['events'])) {
            return;
        }

        try {
            $this->httpClient->post("https://www.google-analytics.com/mp/collect", [
                'query' => [
                    'measurement_id' => $measurementId,
                    'api_secret' => $apiSecret
                ],
                'json' => $payload,
                'timeout' => 5
            ]);

            $this->logger->info('Events sent to Google Analytics', [
                'count' => count($payload['events'])
            ]);
        } catch (\Exception $e) {
            $this->logger->error('Failed to send events to Google Analytics', [
                'error' => $e->getMessage()
            ]);
        }
    }

    private function sendToCustomAnalytics(array $events): void
    {
        $endpoint = $this->config['custom_analytics']['endpoint'];
        $apiKey = $this->config['custom_analytics']['api_key'];

        if (!$endpoint || !$apiKey) {
            return;
        }

        try {
            $this->httpClient->post($endpoint, [
                'json' => [
                    'events' => $events,
                    'batch_id' => uniqid(),
                    'timestamp' => time()
                ],
                'headers' => [
                    'Authorization' => 'Bearer ' . $apiKey,
                    'Content-Type' => 'application/json'
                ],
                'timeout' => 5
            ]);

            $this->logger->info('Events sent to custom analytics', [
                'count' => count($events),
                'endpoint' => $endpoint
            ]);
        } catch (\Exception $e) {
            $this->logger->error('Failed to send events to custom analytics', [
                'error' => $e->getMessage(),
                'endpoint' => $endpoint
            ]);
        }
    }

    private function logEvents(array $events): void
    {
        foreach ($events as $event) {
            $this->logger->info('Analytics event', $event);
        }
    }

    private function convertToGoogleAnalyticsFormat(array $event): ?array
    {
        switch ($event['event_type']) {
            case 'page_view':
                return [
                    'name' => 'page_view',
                    'params' => [
                        'page_title' => $event['page_title'] ?? '',
                        'page_location' => $event['page_url'] ?? '',
                        'custom_parameter_country' => $event['country'],
                        'custom_parameter_language' => $event['language'],
                        'custom_parameter_region' => $event['custom_dimensions']['geo_region'] ?? '',
                        'custom_parameter_city' => $event['custom_dimensions']['geo_city'] ?? ''
                    ]
                ];

            case 'custom_event':
                return [
                    'name' => $event['event_name'],
                    'params' => array_merge($event['parameters'], [
                        'custom_parameter_country' => $event['country'],
                        'custom_parameter_language' => $event['language']
                    ])
                ];

            case 'conversion':
                return [
                    'name' => 'purchase',
                    'params' => [
                        'currency' => $event['currency'],
                        'value' => $event['value'],
                        'custom_parameter_country' => $event['country'],
                        'custom_parameter_language' => $event['language'],
                        'custom_parameter_geo_segment' => $event['custom_dimensions']['conversion_geo_segment'] ?? ''
                    ]
                ];

            default:
                return null;
        }
    }

    private function getClientId(): string
    {
        // Generate or retrieve stable client ID
        $cookieName = '_ga_client_id';

        if (isset($_COOKIE[$cookieName])) {
            return $_COOKIE[$cookieName];
        }

        $clientId = sprintf('%d.%d', random_int(100000000, 999999999), time());
        setcookie($cookieName, $clientId, time() + (365 * 24 * 60 * 60), '/', '', true, true);

        return $clientId;
    }

    private function getClientIP(): string
    {
        $headers = [
            'HTTP_CF_CONNECTING_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];

        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = trim(explode(',', $_SERVER[$header])[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
    }

    private function anonymizeIP(string $ip): string
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            // IPv4: Set last octet to 0
            return preg_replace('/\.\d+$/', '.0', $ip);
        } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            // IPv6: Set last 80 bits to 0
            $parts = explode(':', $ip);
            $parts = array_slice($parts, 0, 3);
            return implode(':', $parts) . '::';
        }

        return '0.0.0.0';
    }

    private function isDNTEnabled(): bool
    {
        return isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] === '1';
    }

    private function getDeviceType(): string
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

        if (preg_match('/Mobile|Android|iPhone|iPad/', $userAgent)) {
            return 'mobile';
        } elseif (preg_match('/Tablet|iPad/', $userAgent)) {
            return 'tablet';
        }

        return 'desktop';
    }

    private function getBrowser(): string
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

        if (strpos($userAgent, 'Chrome') !== false) return 'Chrome';
        if (strpos($userAgent, 'Firefox') !== false) return 'Firefox';
        if (strpos($userAgent, 'Safari') !== false) return 'Safari';
        if (strpos($userAgent, 'Edge') !== false) return 'Edge';
        if (strpos($userAgent, 'Opera') !== false) return 'Opera';

        return 'Unknown';
    }

    private function getOperatingSystem(): string
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

        if (strpos($userAgent, 'Windows') !== false) return 'Windows';
        if (strpos($userAgent, 'Mac') !== false) return 'macOS';
        if (strpos($userAgent, 'Linux') !== false) return 'Linux';
        if (strpos($userAgent, 'Android') !== false) return 'Android';
        if (strpos($userAgent, 'iOS') !== false) return 'iOS';

        return 'Unknown';
    }

    private function getGeoSegment(string $country): string
    {
        $segments = [
            'tier1' => ['US', 'CA', 'GB', 'AU', 'DE', 'FR', 'NL', 'SE', 'NO', 'DK'],
            'tier2' => ['ES', 'IT', 'PT', 'JP', 'KR', 'SG', 'HK'],
            'emerging' => ['BR', 'MX', 'IN', 'CN', 'RU', 'TR', 'ZA'],
            'developing' => [] // Default for others
        ];

        foreach ($segments as $segment => $countries) {
            if (in_array($country, $countries)) {
                return $segment;
            }
        }

        return 'developing';
    }

    public function __destruct()
    {
        // Flush remaining events
        $this->flush();
    }
}

Google Analytics Integration

Enhanced Google Analytics Setup

<?php
// src/GoogleAnalyticsIntegration.php
class GoogleAnalyticsIntegration
{
    private GeographicAnalytics $analytics;
    private array $config;

    public function __construct(GeographicAnalytics $analytics, array $config)
    {
        $this->analytics = $analytics;
        $this->config = $config;
    }

    public function generateTrackingCode(): string
    {
        $measurementId = $this->config['measurement_id'];
        $country = $this->analytics->geo->getCountryCode();
        $language = $this->analytics->geo->getLanguage();

        $config = [
            'custom_map' => [
                'custom_parameter_country' => 'country',
                'custom_parameter_language' => 'language',
                'custom_parameter_region' => 'region',
                'custom_parameter_city' => 'city'
            ],
            'country' => $country,
            'language' => $language,
            'region' => $this->analytics->geo->getRegion(),
            'city' => $this->analytics->geo->getCity()
        ];

        $configJson = json_encode($config, JSON_UNESCAPED_UNICODE);

        return "
        <!-- Google Analytics 4 with Geographic Enhancement -->
        <script async src="https://www.googletagmanager.com/gtag/js?id={$measurementId}"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());

            gtag('config', '{$measurementId}', {$configJson});

            // Enhanced geographic tracking
            gtag('event', 'page_view', {
                'custom_parameter_country': '{$country}',
                'custom_parameter_language': '{$language}',
                'custom_parameter_region': '{$this->analytics->geo->getRegion()}',
                'custom_parameter_city': '{$this->analytics->geo->getCity()}'
            });
        </script>
        ";
    }

    public function trackGeographicEvent(string $eventName, array $parameters = []): void
    {
        $country = $this->analytics->geo->getCountryCode();
        $language = $this->analytics->geo->getLanguage();

        $enhancedParameters = array_merge($parameters, [
            'country' => $country,
            'language' => $language,
            'geo_segment' => $this->analytics->getGeoSegment($country)
        ]);

        $this->analytics->trackEvent($eventName, $enhancedParameters);
    }

    public function trackGeographicConversion(string $conversionType, float $value, string $currency, array $items = []): void
    {
        $country = $this->analytics->geo->getCountryCode();

        // Add geographic context to items
        $enhancedItems = array_map(function($item) use ($country) {
            return array_merge($item, [
                'custom_parameter_source_country' => $country
            ]);
        }, $items);

        $this->analytics->trackConversion($conversionType, $value, $currency, $enhancedItems);
    }

    public function generateCustomDimensions(): array
    {
        return [
            1 => ['name' => 'Geographic Country', 'scope' => 'USER'],
            2 => ['name' => 'Geographic Language', 'scope' => 'USER'],
            3 => ['name' => 'Geographic Region', 'scope' => 'SESSION'],
            4 => ['name' => 'Geographic City', 'scope' => 'SESSION'],
            5 => ['name' => 'Geographic Segment', 'scope' => 'USER'],
            6 => ['name' => 'Device Type', 'scope' => 'SESSION'],
            7 => ['name' => 'Browser', 'scope' => 'SESSION'],
            8 => ['name' => 'Operating System', 'scope' => 'SESSION']
        ];
    }

    public function generateGeographicSegments(): array
    {
        return [
            'high_value_countries' => [
                'name' => 'High Value Countries',
                'definition' => 'Users from Tier 1 countries (US, CA, GB, AU, DE, FR, NL, SE, NO, DK)',
                'countries' => ['US', 'CA', 'GB', 'AU', 'DE', 'FR', 'NL', 'SE', 'NO', 'DK']
            ],
            'emerging_markets' => [
                'name' => 'Emerging Markets',
                'definition' => 'Users from emerging market countries',
                'countries' => ['BR', 'MX', 'IN', 'CN', 'RU', 'TR', 'ZA']
            ],
            'mobile_heavy_regions' => [
                'name' => 'Mobile-Heavy Regions',
                'definition' => 'Regions with >70% mobile usage',
                'countries' => ['IN', 'ID', 'PH', 'VN', 'TH', 'MY']
            ],
            'gdpr_countries' => [
                'name' => 'GDPR Countries',
                'definition' => 'EU countries requiring GDPR compliance',
                'countries' => ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE']
            ]
        ];
    }
}

Custom Event Tracking

Specialized Event Trackers

<?php
// src/SpecializedEventTrackers.php

class EcommerceEventTracker
{
    private GeographicAnalytics $analytics;

    public function __construct(GeographicAnalytics $analytics)
    {
        $this->analytics = $analytics;
    }

    public function trackProductView(array $product): void
    {
        $this->analytics->trackEvent('view_item', [
            'currency' => $product['currency'],
            'value' => $product['price'],
            'item_id' => $product['id'],
            'item_name' => $product['name'],
            'item_category' => $product['category'],
            'price' => $product['price'],
            'quantity' => 1,
            'geographic_pricing_tier' => $this->getPricingTier()
        ]);
    }

    public function trackAddToCart(array $item): void
    {
        $this->analytics->trackEvent('add_to_cart', [
            'currency' => $item['currency'],
            'value' => $item['price'] * $item['quantity'],
            'item_id' => $item['id'],
            'item_name' => $item['name'],
            'quantity' => $item['quantity'],
            'geographic_market_segment' => $this->getMarketSegment()
        ]);
    }

    public function trackPurchase(array $order): void
    {
        $items = array_map(function($item) {
            return [
                'item_id' => $item['id'],
                'item_name' => $item['name'],
                'item_category' => $item['category'],
                'quantity' => $item['quantity'],
                'price' => $item['price']
            ];
        }, $order['items']);

        $this->analytics->trackConversion('purchase', $order['total'], $order['currency'], $items);

        // Additional ecommerce event
        $this->analytics->trackEvent('purchase', [
            'transaction_id' => $order['id'],
            'currency' => $order['currency'],
            'value' => $order['total'],
            'tax' => $order['tax'],
            'shipping' => $order['shipping'],
            'payment_method' => $order['payment_method'],
            'shipping_method' => $order['shipping_method'],
            'items' => $items,
            'geographic_conversion_value_tier' => $this->getConversionValueTier($order['total'])
        ]);
    }

    private function getPricingTier(): string
    {
        $country = $this->analytics->geo->getCountryCode();
        $tiers = [
            'premium' => ['US', 'CA', 'GB', 'AU', 'CH', 'NO'],
            'standard' => ['DE', 'FR', 'ES', 'IT', 'NL', 'SE', 'DK'],
            'value' => ['BR', 'MX', 'RU', 'TR', 'PL'],
            'emerging' => ['IN', 'CN', 'TH', 'VN', 'ID']
        ];

        foreach ($tiers as $tier => $countries) {
            if (in_array($country, $countries)) {
                return $tier;
            }
        }

        return 'standard';
    }

    private function getMarketSegment(): string
    {
        return $this->analytics->getGeoSegment($this->analytics->geo->getCountryCode());
    }

    private function getConversionValueTier(float $value): string
    {
        if ($value >= 500) return 'high';
        if ($value >= 100) return 'medium';
        if ($value >= 25) return 'low';
        return 'micro';
    }
}

class ContentEventTracker
{
    private GeographicAnalytics $analytics;

    public function __construct(GeographicAnalytics $analytics)
    {
        $this->analytics = $analytics;
    }

    public function trackContentView(string $contentType, string $contentId, string $title): void
    {
        $this->analytics->trackEvent('content_view', [
            'content_type' => $contentType,
            'content_id' => $contentId,
            'content_title' => $title,
            'geographic_content_preference' => $this->getContentPreference($contentType)
        ]);
    }

    public function trackLanguageChange(string $fromLanguage, string $toLanguage): void
    {
        $this->analytics->trackEvent('language_change', [
            'from_language' => $fromLanguage,
            'to_language' => $toLanguage,
            'change_reason' => 'user_selection',
            'geographic_language_availability' => $this->getLanguageAvailability()
        ]);
    }

    public function trackSearchQuery(string $query, int $resultCount): void
    {
        $this->analytics->trackEvent('search', [
            'search_term' => $query,
            'result_count' => $resultCount,
            'search_language' => $this->analytics->geo->getLanguage(),
            'geographic_search_intent' => $this->getSearchIntent($query)
        ]);
    }

    private function getContentPreference(string $contentType): string
    {
        $country = $this->analytics->geo->getCountryCode();

        $preferences = [
            'US' => 'video_heavy',
            'JP' => 'image_heavy',
            'DE' => 'text_heavy',
            'CN' => 'interactive',
            'IN' => 'mobile_optimized'
        ];

        return $preferences[$country] ?? 'balanced';
    }

    private function getLanguageAvailability(): array
    {
        $country = $this->analytics->geo->getCountryCode();

        $availability = [
            'US' => ['en', 'es'],
            'CA' => ['en', 'fr'],
            'CH' => ['de', 'fr', 'it', 'en'],
            'BE' => ['fr', 'nl', 'en'],
            'IN' => ['en', 'hi', 'ta', 'bn']
        ];

        return $availability[$country] ?? ['en'];
    }

    private function getSearchIntent(string $query): string
    {
        // Simple intent classification
        if (preg_match('/\b(buy|purchase|price|cost)\b/i', $query)) {
            return 'commercial';
        }
        if (preg_match('/\b(how|what|why|when|where)\b/i', $query)) {
            return 'informational';
        }
        if (preg_match('/\b(near me|location|store|address)\b/i', $query)) {
            return 'local';
        }

        return 'navigational';
    }
}

class UserEngagementTracker
{
    private GeographicAnalytics $analytics;
    private float $sessionStart;

    public function __construct(GeographicAnalytics $analytics)
    {
        $this->analytics = $analytics;
        $this->sessionStart = microtime(true);
    }

    public function trackScrollDepth(int $percentage): void
    {
        $this->analytics->trackEvent('scroll_depth', [
            'scroll_percentage' => $percentage,
            'page_url' => $_SERVER['REQUEST_URI'] ?? '/',
            'geographic_engagement_pattern' => $this->getEngagementPattern()
        ]);
    }

    public function trackTimeOnPage(): void
    {
        $timeSpent = round((microtime(true) - $this->sessionStart) * 1000); // milliseconds

        $this->analytics->trackUserTiming('page_engagement', 'time_on_page', $timeSpent);

        $this->analytics->trackEvent('page_engagement', [
            'time_on_page' => $timeSpent,
            'engagement_level' => $this->getEngagementLevel($timeSpent),
            'geographic_attention_span' => $this->getAttentionSpan()
        ]);
    }

    public function trackFeatureUsage(string $feature): void
    {
        $this->analytics->trackEvent('feature_usage', [
            'feature_name' => $feature,
            'usage_timestamp' => time(),
            'geographic_feature_popularity' => $this->getFeaturePopularity($feature)
        ]);
    }

    private function getEngagementPattern(): string
    {
        $country = $this->analytics->geo->getCountryCode();

        $patterns = [
            'US' => 'quick_scan',
            'DE' => 'thorough_read',
            'JP' => 'detailed_analysis',
            'BR' => 'social_focused',
            'IN' => 'value_seeking'
        ];

        return $patterns[$country] ?? 'standard';
    }

    private function getEngagementLevel(int $timeMs): string
    {
        if ($timeMs < 5000) return 'bounce';
        if ($timeMs < 30000) return 'brief';
        if ($timeMs < 120000) return 'engaged';
        return 'deep';
    }

    private function getAttentionSpan(): string
    {
        $country = $this->analytics->geo->getCountryCode();

        // Based on cultural and market research
        $spans = [
            'US' => 'short',      // 8-12 seconds
            'DE' => 'long',       // 15-20 seconds
            'JP' => 'very_long',  // 20+ seconds
            'CN' => 'medium',     // 10-15 seconds
            'IN' => 'medium'      // 10-15 seconds
        ];

        return $spans[$country] ?? 'medium';
    }

    private function getFeaturePopularity(string $feature): string
    {
        $country = $this->analytics->geo->getCountryCode();

        // Feature popularity by geographic region
        $popularity = [
            'US' => ['chat_support' => 'high', 'video_content' => 'high', 'social_login' => 'medium'],
            'DE' => ['detailed_specs' => 'high', 'privacy_controls' => 'high', 'chat_support' => 'low'],
            'JP' => ['product_reviews' => 'high', 'detailed_images' => 'high', 'comparison_tools' => 'high'],
            'CN' => ['social_sharing' => 'high', 'mobile_payment' => 'high', 'group_buying' => 'high']
        ];

        return $popularity[$country][$feature] ?? 'medium';
    }
}

Geographic Dashboards

Analytics Dashboard Generator

<?php
// src/GeographicDashboard.php
class GeographicDashboard
{
    private PDO $db;
    private CacheItemPoolInterface $cache;

    public function __construct(PDO $db, CacheItemPoolInterface $cache)
    {
        $this->db = $db;
        $this->cache = $cache;
    }

    public function generateDashboardData(int $days = 30): array
    {
        $cacheKey = "dashboard_data_{$days}";
        $cached = $this->cache->getItem($cacheKey);

        if ($cached->isHit()) {
            return $cached->get();
        }

        $data = [
            'overview' => $this->getOverviewMetrics($days),
            'geographic_breakdown' => $this->getGeographicBreakdown($days),
            'traffic_sources' => $this->getTrafficSources($days),
            'conversion_rates' => $this->getConversionRates($days),
            'revenue_by_country' => $this->getRevenueByCountry($days),
            'language_preferences' => $this->getLanguagePreferences($days),
            'device_usage' => $this->getDeviceUsage($days),
            'performance_metrics' => $this->getPerformanceMetrics($days),
            'trends' => $this->getTrends($days)
        ];

        $cached->set($data);
        $cached->expiresAfter(3600); // 1 hour
        $this->cache->save($cached);

        return $data;
    }

    private function getOverviewMetrics(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                COUNT(DISTINCT session_id) as unique_sessions,
                COUNT(*) as total_events,
                COUNT(DISTINCT country) as unique_countries,
                COUNT(DISTINCT CASE WHEN event_type = 'conversion' THEN session_id END) as conversions,
                AVG(CASE WHEN event_type = 'user_timing' AND timing_variable = 'time_on_page' THEN timing_value END) as avg_time_on_page
            FROM analytics_events
            WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
        ");
        $stmt->execute([$days]);

        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }

    private function getGeographicBreakdown(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                country,
                COUNT(DISTINCT session_id) as sessions,
                COUNT(CASE WHEN event_type = 'page_view' THEN 1 END) as pageviews,
                COUNT(CASE WHEN event_type = 'conversion' THEN 1 END) as conversions,
                ROUND(COUNT(CASE WHEN event_type = 'conversion' THEN 1 END) * 100.0 / COUNT(DISTINCT session_id), 2) as conversion_rate,
                SUM(CASE WHEN event_type = 'conversion' THEN value ELSE 0 END) as revenue
            FROM analytics_events
            WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY country
            ORDER BY sessions DESC
            LIMIT 20
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getTrafficSources(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                country,
                CASE
                    WHEN referrer LIKE '%google%' THEN 'Google'
                    WHEN referrer LIKE '%facebook%' THEN 'Facebook'
                    WHEN referrer LIKE '%youtube%' THEN 'YouTube'
                    WHEN referrer LIKE '%twitter%' THEN 'Twitter'
                    WHEN referrer = '' THEN 'Direct'
                    ELSE 'Other'
                END as source,
                COUNT(DISTINCT session_id) as sessions
            FROM analytics_events
            WHERE event_type = 'page_view'
            AND timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY country, source
            ORDER BY country, sessions DESC
        ");
        $stmt->execute([$days]);

        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Group by country
        $grouped = [];
        foreach ($results as $row) {
            $grouped[$row['country']][] = [
                'source' => $row['source'],
                'sessions' => $row['sessions']
            ];
        }

        return $grouped;
    }

    private function getConversionRates(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                country,
                DATE(timestamp) as date,
                COUNT(DISTINCT session_id) as sessions,
                COUNT(CASE WHEN event_type = 'conversion' THEN 1 END) as conversions,
                ROUND(COUNT(CASE WHEN event_type = 'conversion' THEN 1 END) * 100.0 / COUNT(DISTINCT session_id), 2) as conversion_rate
            FROM analytics_events
            WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY country, DATE(timestamp)
            ORDER BY date DESC, conversion_rate DESC
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getRevenueByCountry(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                country,
                currency,
                SUM(value) as total_revenue,
                COUNT(CASE WHEN event_type = 'conversion' THEN 1 END) as total_conversions,
                ROUND(SUM(value) / COUNT(CASE WHEN event_type = 'conversion' THEN 1 END), 2) as avg_order_value
            FROM analytics_events
            WHERE event_type = 'conversion'
            AND timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY country, currency
            ORDER BY total_revenue DESC
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getLanguagePreferences(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                language,
                country,
                COUNT(DISTINCT session_id) as sessions,
                COUNT(CASE WHEN event_name = 'language_change' THEN 1 END) as language_changes
            FROM analytics_events
            WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY language, country
            ORDER BY sessions DESC
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getDeviceUsage(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                country,
                JSON_EXTRACT(custom_dimensions, '$.device_type') as device_type,
                JSON_EXTRACT(custom_dimensions, '$.browser') as browser,
                JSON_EXTRACT(custom_dimensions, '$.os') as os,
                COUNT(DISTINCT session_id) as sessions
            FROM analytics_events
            WHERE event_type = 'page_view'
            AND timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            AND custom_dimensions IS NOT NULL
            GROUP BY country, device_type, browser, os
            ORDER BY sessions DESC
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getPerformanceMetrics(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                country,
                AVG(CASE WHEN timing_variable = 'time_on_page' THEN timing_value END) as avg_time_on_page,
                AVG(CASE WHEN timing_variable = 'page_load_time' THEN timing_value END) as avg_page_load_time,
                COUNT(CASE WHEN event_name = 'scroll_depth' AND JSON_EXTRACT(parameters, '$.scroll_percentage') >= 75 THEN 1 END) as deep_scrolls
            FROM analytics_events
            WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY country
            ORDER BY avg_time_on_page DESC
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getTrends(int $days): array
    {
        $stmt = $this->db->prepare("
            SELECT
                DATE(timestamp) as date,
                country,
                COUNT(DISTINCT session_id) as sessions,
                COUNT(CASE WHEN event_type = 'conversion' THEN 1 END) as conversions,
                SUM(CASE WHEN event_type = 'conversion' THEN value ELSE 0 END) as revenue
            FROM analytics_events
            WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ? DAY)
            GROUP BY DATE(timestamp), country
            ORDER BY date DESC, sessions DESC
        ");
        $stmt->execute([$days]);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function generateReport(int $days = 30): string
    {
        $data = $this->generateDashboardData($days);

        $report = "# Geographic Analytics Report

";
        $report .= "**Report Period**: Last {$days} days
";
        $report .= "**Generated**: " . date('Y-m-d H:i:s T') . "

";

        // Overview
        $overview = $data['overview'];
        $report .= "## Overview

";
        $report .= "- **Total Sessions**: " . number_format($overview['unique_sessions']) . "
";
        $report .= "- **Total Events**: " . number_format($overview['total_events']) . "
";
        $report .= "- **Countries Reached**: " . $overview['unique_countries'] . "
";
        $report .= "- **Conversions**: " . number_format($overview['conversions']) . "
";
        $report .= "- **Avg Time on Page**: " . round($overview['avg_time_on_page'] / 1000, 1) . "s

";

        // Top Countries
        $report .= "## Top Countries by Sessions

";
        $report .= "| Country | Sessions | Pageviews | Conversions | Conversion Rate | Revenue |
";
        $report .= "|---------|----------|-----------|-------------|-----------------|----------|
";

        foreach (array_slice($data['geographic_breakdown'], 0, 10) as $country) {
            $report .= sprintf(
                "| %s | %s | %s | %s | %s%% | $%s |
",
                $country['country'],
                number_format($country['sessions']),
                number_format($country['pageviews']),
                number_format($country['conversions']),
                $country['conversion_rate'],
                number_format($country['revenue'], 2)
            );
        }

        $report .= "
";

        // Revenue by Country
        $report .= "## Revenue by Country

";
        $report .= "| Country | Currency | Total Revenue | Conversions | AOV |
";
        $report .= "|---------|----------|---------------|-------------|-----|
";

        foreach (array_slice($data['revenue_by_country'], 0, 10) as $revenue) {
            $report .= sprintf(
                "| %s | %s | %s | %s | %s |
",
                $revenue['country'],
                $revenue['currency'],
                number_format($revenue['total_revenue'], 2),
                number_format($revenue['total_conversions']),
                number_format($revenue['avg_order_value'], 2)
            );
        }

        return $report;
    }
}

// Database schema for analytics events
/*
CREATE TABLE analytics_events (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    session_id VARCHAR(255) NOT NULL,
    user_id VARCHAR(255) NULL,
    event_type ENUM('page_view', 'custom_event', 'conversion', 'user_timing') NOT NULL,
    event_name VARCHAR(255) NULL,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    country CHAR(2) NOT NULL,
    language CHAR(2) NOT NULL,
    page_url TEXT,
    referrer TEXT,
    user_agent TEXT,
    value DECIMAL(10,2) DEFAULT 0,
    currency CHAR(3) DEFAULT 'USD',
    parameters JSON,
    custom_dimensions JSON,
    timing_category VARCHAR(100) NULL,
    timing_variable VARCHAR(100) NULL,
    timing_value INT NULL,

    INDEX idx_timestamp (timestamp),
    INDEX idx_country (country),
    INDEX idx_session (session_id),
    INDEX idx_event_type (event_type),
    INDEX idx_country_timestamp (country, timestamp)
);
*/

Next Steps


Previous: Geographic Content Delivery | Next: API Development Patterns

Clone this wiki locally