330 lines
8.8 KiB
PHP
330 lines
8.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Analytics;
|
|
|
|
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
|
use App\Framework\Http\ServerEnvironment;
|
|
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
|
|
use App\Framework\Performance\PerformanceCategory;
|
|
use App\Framework\Random\RandomGenerator;
|
|
|
|
/**
|
|
* Moderner Analytics-Collector basierend auf dem Performance-System
|
|
*
|
|
* Sammelt Business- und User-Analytics-Daten und nutzt das bewährte
|
|
* Performance-Framework für effiziente Datensammlung.
|
|
*/
|
|
final class AnalyticsCollector
|
|
{
|
|
/** @var array<string, mixed> */
|
|
private array $sessionData = [];
|
|
|
|
/** @var array<string, int> */
|
|
private array $counters = [];
|
|
|
|
public function __construct(
|
|
private readonly PerformanceCollectorInterface $performanceCollector,
|
|
private readonly AnalyticsStorage $storage,
|
|
private readonly RandomGenerator $random,
|
|
private readonly ServerEnvironment $serverEnvironment,
|
|
private readonly bool $enabled = true,
|
|
private readonly float $samplingRate = 1.0
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Trackt eine Benutzeraktion
|
|
*/
|
|
public function trackAction(
|
|
string $action,
|
|
AnalyticsCategory $category,
|
|
array $properties = []
|
|
): void {
|
|
error_log("Analytics: trackAction called with action={$action}, category={$category->value}");
|
|
|
|
if (! $this->shouldTrack()) {
|
|
error_log("Analytics: trackAction skipped (shouldTrack=false)");
|
|
|
|
return;
|
|
}
|
|
|
|
$context = array_merge([
|
|
'action' => $action,
|
|
'category' => $category->value,
|
|
'session_id' => $this->getSessionId(),
|
|
'timestamp' => time(),
|
|
'user_agent' => (string) $this->serverEnvironment->getUserAgent(),
|
|
'ip' => (string) $this->serverEnvironment->getClientIp(),
|
|
], $properties);
|
|
|
|
// Nutze Performance-System für Analytics
|
|
$this->performanceCollector->recordMetric(
|
|
"analytics_action_{$action}",
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$context
|
|
);
|
|
|
|
$this->performanceCollector->increment(
|
|
'analytics_actions_total',
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$context
|
|
);
|
|
|
|
// Speichere in Analytics-Storage
|
|
error_log("Analytics: About to call storage->storeRawData and storeAggregatedData");
|
|
$this->storage->storeRawData($context, $this->samplingRate);
|
|
$this->storeAggregatedData('user_actions', $action, $context);
|
|
}
|
|
|
|
/**
|
|
* Trackt eine Seitenansicht
|
|
*/
|
|
public function trackPageView(
|
|
string $path,
|
|
string $title = '',
|
|
array $properties = []
|
|
): void {
|
|
if (! $this->shouldTrack()) {
|
|
return;
|
|
}
|
|
|
|
$context = array_merge([
|
|
'path' => $path,
|
|
'title' => $title,
|
|
'referer' => $this->serverEnvironment->getReferer(),
|
|
'session_id' => $this->getSessionId(),
|
|
'timestamp' => time(),
|
|
], $properties);
|
|
|
|
$this->performanceCollector->recordMetric(
|
|
'analytics_page_view',
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$context
|
|
);
|
|
|
|
$this->performanceCollector->increment(
|
|
'analytics_page_views_total',
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$context
|
|
);
|
|
|
|
// Speichere in Analytics-Storage
|
|
$this->storage->storeRawData($context, $this->samplingRate);
|
|
$this->storeAggregatedData('page_views', $path, $context);
|
|
}
|
|
|
|
/**
|
|
* Trackt einen Fehler
|
|
*/
|
|
public function trackError(
|
|
string $errorType,
|
|
string $message,
|
|
array $context = []
|
|
): void {
|
|
if (! $this->shouldTrack()) {
|
|
return;
|
|
}
|
|
|
|
$errorContext = array_merge([
|
|
'error_type' => $errorType,
|
|
'message' => $message,
|
|
'session_id' => $this->getSessionId(),
|
|
'timestamp' => time(),
|
|
'url' => (string) $this->serverEnvironment->getRequestUri(),
|
|
], $context);
|
|
|
|
$this->performanceCollector->recordMetric(
|
|
"analytics_error_{$errorType}",
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$errorContext
|
|
);
|
|
|
|
$this->performanceCollector->increment(
|
|
'analytics_errors_total',
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$errorContext
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Trackt ein Business-Event (Conversion, Purchase, etc.)
|
|
*/
|
|
public function trackBusinessEvent(
|
|
string $event,
|
|
float $value = 0.0,
|
|
string $currency = 'EUR',
|
|
array $properties = []
|
|
): void {
|
|
if (! $this->shouldTrack()) {
|
|
return;
|
|
}
|
|
|
|
$context = array_merge([
|
|
'event' => $event,
|
|
'value' => $value,
|
|
'currency' => $currency,
|
|
'session_id' => $this->getSessionId(),
|
|
'timestamp' => time(),
|
|
], $properties);
|
|
|
|
$this->performanceCollector->recordMetric(
|
|
"analytics_business_{$event}",
|
|
PerformanceCategory::CUSTOM,
|
|
$value,
|
|
$context
|
|
);
|
|
|
|
$this->performanceCollector->increment(
|
|
'analytics_business_events_total',
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Trackt API-Nutzung
|
|
*/
|
|
public function trackApiCall(
|
|
string $endpoint,
|
|
string $method,
|
|
int $responseCode,
|
|
float $responseTime,
|
|
array $properties = []
|
|
): void {
|
|
if (! $this->shouldTrack()) {
|
|
return;
|
|
}
|
|
|
|
$context = array_merge([
|
|
'endpoint' => $endpoint,
|
|
'method' => $method,
|
|
'response_code' => $responseCode,
|
|
'response_time' => $responseTime,
|
|
'session_id' => $this->getSessionId(),
|
|
'timestamp' => time(),
|
|
], $properties);
|
|
|
|
$this->performanceCollector->recordMetric(
|
|
'analytics_api_call',
|
|
PerformanceCategory::CUSTOM,
|
|
$responseTime,
|
|
$context
|
|
);
|
|
|
|
$this->performanceCollector->increment(
|
|
'analytics_api_calls_total',
|
|
PerformanceCategory::CUSTOM,
|
|
1,
|
|
$context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Setzt Session-Daten
|
|
*/
|
|
public function setSessionData(string $key, mixed $value): void
|
|
{
|
|
$this->sessionData[$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* Holt Session-Daten
|
|
*/
|
|
public function getSessionData(string $key): mixed
|
|
{
|
|
return $this->sessionData[$key] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Erhöht einen Counter
|
|
*/
|
|
public function incrementCounter(string $name, int $amount = 1): void
|
|
{
|
|
if (! isset($this->counters[$name])) {
|
|
$this->counters[$name] = 0;
|
|
}
|
|
$this->counters[$name] += $amount;
|
|
}
|
|
|
|
/**
|
|
* Holt Counter-Wert
|
|
*/
|
|
public function getCounter(string $name): int
|
|
{
|
|
return $this->counters[$name] ?? 0;
|
|
}
|
|
|
|
/**
|
|
* Holt alle Counter
|
|
*/
|
|
public function getCounters(): array
|
|
{
|
|
return $this->counters;
|
|
}
|
|
|
|
/**
|
|
* Prüft ob Tracking durchgeführt werden soll (Sampling)
|
|
*/
|
|
private function shouldTrack(): bool
|
|
{
|
|
error_log("Analytics: shouldTrack check - enabled={$this->enabled}, samplingRate={$this->samplingRate}");
|
|
|
|
if (! $this->enabled) {
|
|
error_log("Analytics: Tracking disabled");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ($this->samplingRate >= 1.0) {
|
|
error_log("Analytics: Tracking enabled (no sampling)");
|
|
|
|
return true;
|
|
}
|
|
|
|
return $this->random->float(0, 1) <= $this->samplingRate;
|
|
}
|
|
|
|
/**
|
|
* Generiert oder holt Session-ID
|
|
*/
|
|
private function getSessionId(): string
|
|
{
|
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
|
return session_id();
|
|
}
|
|
|
|
// Fallback: Cookie-basierte Session-ID
|
|
if (isset($_COOKIE['analytics_session'])) {
|
|
return $_COOKIE['analytics_session'];
|
|
}
|
|
|
|
// Generiere neue Session-ID (Cookie-Setzung erfolgt über Response-System)
|
|
// Hier nur ID generieren, nicht setzen um "headers already sent" zu vermeiden
|
|
return bin2hex($this->random->bytes(16));
|
|
}
|
|
|
|
/**
|
|
* Speichert aggregierte Daten im Storage
|
|
*/
|
|
private function storeAggregatedData(string $category, string $action, array $context): void
|
|
{
|
|
$period = 'hour'; // Standard-Aggregations-Periode
|
|
$aggregatedData = [
|
|
"{$category}_total" => 1,
|
|
"{$category}_{$action}" => 1,
|
|
'unique_sessions' => [$context['session_id'] ?? 'unknown' => 1],
|
|
];
|
|
|
|
$this->storage->storeAggregated($period, $aggregatedData);
|
|
}
|
|
}
|