-
Notifications
You must be signed in to change notification settings - Fork 0
Analytics
Rumen Damyanov edited this page Jul 31, 2025
·
1 revision
Comprehensive guide for tracking geographic data, user behavior analytics, and business intelligence using php-geolocation with popular analytics platforms.
- Analytics Strategy
- Google Analytics Integration
- Custom Event Tracking
- Geographic Dashboards
- User Journey Analytics
- Performance Metrics
- Business Intelligence
- Privacy Compliance
- Real-time Analytics
- Reporting and Visualization
<?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();
}
}<?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']
]
];
}
}<?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';
}
}<?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)
);
*/- 🎯 API Development Patterns - RESTful APIs with geolocation
- 🔧 Configuration Reference - Complete configuration options
⚠️ Error Handling - Comprehensive error handling patterns
Previous: Geographic Content Delivery | Next: API Development Patterns