chore: complete update
This commit is contained in:
89
src/Framework/Analytics/AnalyticsEvent.php
Normal file
89
src/Framework/Analytics/AnalyticsEvent.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics;
|
||||
|
||||
final readonly class AnalyticsEvent
|
||||
{
|
||||
public function __construct(
|
||||
public string $type,
|
||||
public string $name,
|
||||
public array $data = [],
|
||||
public array $context = [],
|
||||
public ?string $userId = null,
|
||||
public ?string $sessionId = null,
|
||||
public ?\DateTimeImmutable $timestamp = null
|
||||
) {}
|
||||
|
||||
public static function pageView(string $path, array $context = []): self
|
||||
{
|
||||
return new self(
|
||||
type: 'page_view',
|
||||
name: $path,
|
||||
context: $context,
|
||||
timestamp: new \DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
|
||||
public static function userAction(string $action, string $userId, array $data = []): self
|
||||
{
|
||||
return new self(
|
||||
type: 'user_action',
|
||||
name: $action,
|
||||
data: $data,
|
||||
userId: $userId,
|
||||
timestamp: new \DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
|
||||
public static function performance(string $metric, float $value, array $context = []): self
|
||||
{
|
||||
return new self(
|
||||
type: 'performance',
|
||||
name: $metric,
|
||||
data: ['value' => $value],
|
||||
context: $context,
|
||||
timestamp: new \DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
|
||||
public function withUserId(string $userId): self
|
||||
{
|
||||
return new self(
|
||||
$this->type,
|
||||
$this->name,
|
||||
$this->data,
|
||||
$this->context,
|
||||
$userId,
|
||||
$this->sessionId,
|
||||
$this->timestamp
|
||||
);
|
||||
}
|
||||
|
||||
public function withSessionId(string $sessionId): self
|
||||
{
|
||||
return new self(
|
||||
$this->type,
|
||||
$this->name,
|
||||
$this->data,
|
||||
$this->context,
|
||||
$this->userId,
|
||||
$sessionId,
|
||||
$this->timestamp
|
||||
);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'name' => $this->name,
|
||||
'data' => $this->data,
|
||||
'context' => $this->context,
|
||||
'user_id' => $this->userId,
|
||||
'session_id' => $this->sessionId,
|
||||
'timestamp' => $this->timestamp?->format(\DateTimeInterface::ATOM)
|
||||
];
|
||||
}
|
||||
}
|
||||
20
src/Framework/Analytics/AnalyticsInterface.php
Normal file
20
src/Framework/Analytics/AnalyticsInterface.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics;
|
||||
|
||||
interface AnalyticsInterface
|
||||
{
|
||||
public function track(AnalyticsEvent $event): void;
|
||||
|
||||
public function trackPageView(string $path, array $context = []): void;
|
||||
|
||||
public function trackUserAction(string $action, string $userId, array $data = []): void;
|
||||
|
||||
public function trackPerformance(string $metric, float $value, array $context = []): void;
|
||||
|
||||
public function flush(): void;
|
||||
|
||||
public function isEnabled(): bool;
|
||||
}
|
||||
111
src/Framework/Analytics/AnalyticsService.php
Normal file
111
src/Framework/Analytics/AnalyticsService.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics;
|
||||
|
||||
use App\Framework\Queue\Queue;
|
||||
use App\Framework\Context\ExecutionContext;
|
||||
use App\Framework\Config\Configuration;
|
||||
|
||||
final class AnalyticsService implements AnalyticsInterface
|
||||
{
|
||||
private bool $enabled;
|
||||
private array $context = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly Queue $queue,
|
||||
private readonly ExecutionContext $executionContext,
|
||||
private readonly Configuration $config
|
||||
) {
|
||||
$this->enabled = $this->config->get('analytics.enabled', false);
|
||||
$this->initializeContext();
|
||||
}
|
||||
|
||||
public function track(AnalyticsEvent $event): void
|
||||
{
|
||||
#if (!$this->enabled) {
|
||||
# return;
|
||||
#}
|
||||
|
||||
$enrichedEvent = $this->enrichEvent($event);
|
||||
|
||||
// Queue für asynchrone Verarbeitung
|
||||
$this->queue->push('analytics', $enrichedEvent->toArray());
|
||||
}
|
||||
|
||||
public function trackPageView(string $path, array $context = []): void
|
||||
{
|
||||
$this->track(AnalyticsEvent::pageView($path, array_merge($this->context, $context)));
|
||||
}
|
||||
|
||||
public function trackUserAction(string $action, string $userId, array $data = []): void
|
||||
{
|
||||
$this->track(AnalyticsEvent::userAction($action, $userId, $data));
|
||||
}
|
||||
|
||||
public function trackPerformance(string $metric, float $value, array $context = []): void
|
||||
{
|
||||
$this->track(AnalyticsEvent::performance($metric, $value, array_merge($this->context, $context)));
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
{
|
||||
// Queue-basiert - kein explizites Flush nötig
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
private function enrichEvent(AnalyticsEvent $event): AnalyticsEvent
|
||||
{
|
||||
$sessionId = $this->executionContext->getSessionId();
|
||||
$userId = $this->executionContext->getUserId();
|
||||
|
||||
$enrichedEvent = $event;
|
||||
|
||||
if ($sessionId && !$event->sessionId) {
|
||||
$enrichedEvent = $enrichedEvent->withSessionId($sessionId);
|
||||
}
|
||||
|
||||
if ($userId && !$event->userId) {
|
||||
$enrichedEvent = $enrichedEvent->withUserId($userId);
|
||||
}
|
||||
|
||||
return $enrichedEvent;
|
||||
}
|
||||
|
||||
private function initializeContext(): void
|
||||
{
|
||||
$this->context = [
|
||||
'environment' => $this->config->get('app.environment', 'production'),
|
||||
'version' => $this->config->get('app.version', '1.0.0'),
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
|
||||
'ip_address' => $this->getClientIp(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getClientIp(): ?string
|
||||
{
|
||||
$headers = [
|
||||
'HTTP_CF_CONNECTING_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED',
|
||||
'REMOTE_ADDR'
|
||||
];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (!empty($_SERVER[$header])) {
|
||||
$ips = explode(',', $_SERVER[$header]);
|
||||
return trim($ips[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
62
src/Framework/Analytics/Middleware/AnalyticsMiddleware.php
Normal file
62
src/Framework/Analytics/Middleware/AnalyticsMiddleware.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics\Middleware;
|
||||
|
||||
use App\Framework\Analytics\AnalyticsInterface;
|
||||
use App\Framework\Analytics\AnalyticsService;
|
||||
use App\Framework\Config\Configuration;
|
||||
use App\Framework\Context\ExecutionContext;
|
||||
use App\Framework\Http\HttpMiddleware;
|
||||
use App\Framework\Http\MiddlewareContext;
|
||||
use App\Framework\Http\MiddlewarePriority;
|
||||
use App\Framework\Http\MiddlewarePriorityAttribute;
|
||||
use App\Framework\Http\RequestStateManager;
|
||||
use App\Framework\Performance\PerformanceMeter;
|
||||
use App\Framework\Queue\FileQueue;
|
||||
|
||||
#[MiddlewarePriorityAttribute(MiddlewarePriority::BUSINESS_LOGIC)]
|
||||
final readonly class AnalyticsMiddleware implements HttpMiddleware
|
||||
{
|
||||
private AnalyticsService $analytics;
|
||||
public function __construct(
|
||||
#private AnalyticsInterface $analytics = new AnalyticsService($queue, $executionContext, $config),
|
||||
private ExecutionContext $executionContext,
|
||||
private Configuration $config,
|
||||
private PerformanceMeter $performanceMeter
|
||||
) {
|
||||
$this->analytics = new AnalyticsService(new FileQueue(__DIR__.'/analytics-data'), $this->executionContext, $this->config);
|
||||
}
|
||||
|
||||
public function __invoke(MiddlewareContext $context, callable $next, RequestStateManager $stateManager): MiddlewareContext
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
|
||||
$request = $context->request;
|
||||
|
||||
// Track page view
|
||||
/*$this->analytics->trackPageView(
|
||||
$context->request->path,
|
||||
[
|
||||
'method' => $request->method->value,
|
||||
'query_params' => $request->queryParams,
|
||||
'referer' => $request->headers->get('Referer')
|
||||
]
|
||||
);*/
|
||||
|
||||
$context = $next($context);
|
||||
|
||||
// Track response performance
|
||||
$responseTime = (microtime(true) - $startTime) * 1000;
|
||||
|
||||
/*$this->analytics->trackPerformance('response_time', $responseTime, [
|
||||
'path' => $request->path,
|
||||
'method' => $request->method->value,
|
||||
'status_code' => $context->response->status->value
|
||||
]);*/
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
31
src/Framework/Analytics/Queue/AnalyticsProcessor.php
Normal file
31
src/Framework/Analytics/Queue/AnalyticsProcessor.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics\Queue;
|
||||
|
||||
use App\Framework\Queue\QueueProcessor;
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorageInterface;
|
||||
|
||||
final readonly class AnalyticsProcessor implements QueueProcessor
|
||||
{
|
||||
public function __construct(
|
||||
private AnalyticsStorageInterface $storage
|
||||
) {}
|
||||
|
||||
public function canProcess(string $queue): bool
|
||||
{
|
||||
return $queue === 'analytics';
|
||||
}
|
||||
|
||||
public function process(array $payload): void
|
||||
{
|
||||
$this->storage->store($payload);
|
||||
}
|
||||
|
||||
public function failed(array $payload, \Throwable $exception): void
|
||||
{
|
||||
// Log failed analytics events
|
||||
error_log("Analytics processing failed: " . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics\Storage;
|
||||
|
||||
interface AnalyticsStorageInterface
|
||||
{
|
||||
public function store(array $eventData): void;
|
||||
|
||||
public function query(array $filters = []): array;
|
||||
|
||||
public function aggregate(string $metric, array $filters = []): array;
|
||||
}
|
||||
68
src/Framework/Analytics/Storage/FileAnalyticsStorage.php
Normal file
68
src/Framework/Analytics/Storage/FileAnalyticsStorage.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Analytics\Storage;
|
||||
|
||||
final class FileAnalyticsStorage implements AnalyticsStorageInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $storagePath
|
||||
) {
|
||||
if (!is_dir($this->storagePath)) {
|
||||
mkdir($this->storagePath, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(array $eventData): void
|
||||
{
|
||||
$date = date('Y-m-d');
|
||||
$filename = $this->storagePath . "/analytics-{$date}.jsonl";
|
||||
|
||||
$line = json_encode($eventData) . "\n";
|
||||
file_put_contents($filename, $line, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
public function query(array $filters = []): array
|
||||
{
|
||||
// Einfache Implementation - kann erweitert werden
|
||||
$files = glob($this->storagePath . '/analytics-*.jsonl');
|
||||
$events = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$lines = file($file, FILE_IGNORE_NEW_LINES);
|
||||
foreach ($lines as $line) {
|
||||
$event = json_decode($line, true);
|
||||
if ($this->matchesFilters($event, $filters)) {
|
||||
$events[] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
public function aggregate(string $metric, array $filters = []): array
|
||||
{
|
||||
$events = $this->query($filters);
|
||||
|
||||
// Einfache Aggregation - kann erweitert werden
|
||||
$aggregated = [];
|
||||
foreach ($events as $event) {
|
||||
$key = $event['type'] ?? 'unknown';
|
||||
$aggregated[$key] = ($aggregated[$key] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $aggregated;
|
||||
}
|
||||
|
||||
private function matchesFilters(array $event, array $filters): bool
|
||||
{
|
||||
foreach ($filters as $key => $value) {
|
||||
if (!isset($event[$key]) || $event[$key] !== $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user