chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

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

View 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;
}

View 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;
}
}

View 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;
}
}

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

View File

@@ -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;
}

View 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;
}
}