docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,249 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging\ValueObjects;
use App\Framework\Tracing\TraceContext;
/**
* Strukturierte Logging-Kontext Value Object für typsichere Structured Logging.
*
* Ermöglicht strukturierte, maschinenlesbare Log-Daten mit Korrelations-IDs,
* Tags und verschiedenen Kontext-Typen für moderne Observability.
*
* Nutzt das bestehende Framework Tracing-System für Request-Korrelation.
*/
final readonly class LogContext
{
/**
* @param array<string, mixed> $structured Strukturierte Daten (key-value pairs)
* @param string[] $tags Tags für Kategorisierung und Filterung
* @param TraceContext|null $trace Framework Tracing-Kontext für Request-Korrelation
* @param UserContext|null $user Benutzer-Kontext für Audit-Logs
* @param RequestContext|null $request HTTP-Request-Kontext
* @param array<string, mixed> $metadata Zusätzliche Metadaten
*/
public function __construct(
public array $structured = [],
public array $tags = [],
public ?TraceContext $trace = null,
public ?UserContext $user = null,
public ?RequestContext $request = null,
public array $metadata = []
) {
}
/**
* Erstellt leeren LogContext
*/
public static function empty(): self
{
return new self();
}
/**
* Erstellt LogContext mit strukturierten Daten
*/
public static function withData(array $data): self
{
return new self(structured: $data);
}
/**
* Erstellt LogContext mit Tags
*/
public static function withTags(string ...$tags): self
{
return new self(tags: $tags);
}
/**
* Fügt strukturierte Daten hinzu
*/
public function addData(string $key, mixed $value): self
{
return new self(
structured: array_merge($this->structured, [$key => $value]),
tags: $this->tags,
trace: $this->trace,
user: $this->user,
request: $this->request,
metadata: $this->metadata
);
}
/**
* Fügt mehrere strukturierte Daten hinzu
*/
public function mergeData(array $data): self
{
return new self(
structured: array_merge($this->structured, $data),
tags: $this->tags,
trace: $this->trace,
user: $this->user,
request: $this->request,
metadata: $this->metadata
);
}
/**
* Fügt Tags hinzu
*/
public function addTags(string ...$tags): self
{
return new self(
structured: $this->structured,
tags: array_values(array_unique(array_merge($this->tags, $tags))),
trace: $this->trace,
user: $this->user,
request: $this->request,
metadata: $this->metadata
);
}
/**
* Erstellt LogContext mit aktuellem Tracing-Kontext
*/
public static function withCurrentTrace(): self
{
return new self(trace: TraceContext::current());
}
/**
* Setzt Trace-Kontext
*/
public function withTrace(TraceContext $trace): self
{
return new self(
structured: $this->structured,
tags: $this->tags,
trace: $trace,
user: $this->user,
request: $this->request,
metadata: $this->metadata
);
}
/**
* Setzt User-Kontext
*/
public function withUser(UserContext $user): self
{
return new self(
structured: $this->structured,
tags: $this->tags,
trace: $this->trace,
user: $user,
request: $this->request,
metadata: $this->metadata
);
}
/**
* Setzt Request-Kontext
*/
public function withRequest(RequestContext $request): self
{
return new self(
structured: $this->structured,
tags: $this->tags,
trace: $this->trace,
user: $this->user,
request: $request,
metadata: $this->metadata
);
}
/**
* Fügt Metadaten hinzu
*/
public function addMetadata(string $key, mixed $value): self
{
return new self(
structured: $this->structured,
tags: $this->tags,
trace: $this->trace,
user: $this->user,
request: $this->request,
metadata: array_merge($this->metadata, [$key => $value])
);
}
/**
* Kombiniert zwei LogContexts
*/
public function merge(self $other): self
{
return new self(
structured: array_merge($this->structured, $other->structured),
tags: array_unique(array_merge($this->tags, $other->tags)),
trace: $other->trace ?? $this->trace,
user: $other->user ?? $this->user,
request: $other->request ?? $this->request,
metadata: array_merge($this->metadata, $other->metadata)
);
}
/**
* Prüft, ob Context strukturierte Daten hat
*/
public function hasStructuredData(): bool
{
return ! empty($this->structured);
}
/**
* Prüft, ob Context Tags hat
*/
public function hasTags(): bool
{
return ! empty($this->tags);
}
/**
* Prüft, ob bestimmter Tag vorhanden ist
*/
public function hasTag(string $tag): bool
{
return in_array($tag, $this->tags, true);
}
/**
* Konvertiert zu Array für Serialisierung
*/
public function toArray(): array
{
$result = [];
if (! empty($this->structured)) {
$result['structured'] = $this->structured;
}
if (! empty($this->tags)) {
$result['tags'] = $this->tags;
}
if ($this->trace !== null) {
$result['trace'] = [
'trace_id' => $this->trace->getTraceId(),
'active_span' => $this->trace->getActiveSpan()?->toArray(),
];
}
if ($this->user !== null) {
$result['user'] = $this->user->toArray();
}
if ($this->request !== null) {
$result['request'] = $this->request->toArray();
}
if (! empty($this->metadata)) {
$result['metadata'] = $this->metadata;
}
return $result;
}
}

View File

@@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging\ValueObjects;
use App\Framework\Exception\RequestContext as FrameworkRequestContext;
use App\Framework\Http\RequestId;
use App\Framework\UserAgent\UserAgent;
/**
* Request-Kontext Value Object für Structured Logging.
*
* Erweitert den bestehenden Framework RequestContext um
* Logging-spezifische Funktionalität.
*/
final readonly class RequestContext
{
public function __construct(
private FrameworkRequestContext $requestContext
) {
}
/**
* Erstellt von Framework RequestContext
*/
public static function fromFrameworkContext(FrameworkRequestContext $context): self
{
return new self($context);
}
/**
* Erstellt aus Global-Variablen
*/
public static function fromGlobals(): self
{
return new self(FrameworkRequestContext::fromGlobals());
}
/**
* Erstellt leeren Kontext
*/
public static function empty(): self
{
return new self(FrameworkRequestContext::empty());
}
/**
* Erstellt mit spezifischen Daten
*/
public static function create(
?string $clientIp = null,
?UserAgent $userAgent = null,
?string $requestMethod = null,
?string $requestUri = null,
?string $hostIp = null,
?string $hostname = null,
?string $protocol = null,
?string $port = null,
?RequestId $requestId = null,
array $headers = []
): self {
return new self(FrameworkRequestContext::create(
clientIp: $clientIp,
userAgent: $userAgent,
requestMethod: $requestMethod,
requestUri: $requestUri,
hostIp: $hostIp,
hostname: $hostname,
protocol: $protocol,
port: $port,
requestId: $requestId,
headers: $headers
));
}
/**
* Konvertiert zu Array für Logging
*/
public function toArray(): array
{
return $this->requestContext->toArray();
}
/**
* Gibt Request-ID zurück
*/
public function getRequestId(): ?string
{
return $this->requestContext->requestId?->toString();
}
/**
* Gibt Client-IP zurück
*/
public function getClientIp(): ?string
{
return $this->requestContext->clientIp;
}
/**
* Gibt User-Agent zurück
*/
public function getUserAgent(): ?string
{
return $this->requestContext->userAgent?->value;
}
/**
* Gibt Request-Method zurück
*/
public function getMethod(): ?string
{
return $this->requestContext->requestMethod;
}
/**
* Gibt Request-URI zurück
*/
public function getUri(): ?string
{
return $this->requestContext->requestUri;
}
/**
* Gibt Host zurück
*/
public function getHost(): ?string
{
return $this->requestContext->hostname;
}
/**
* Gibt Protocol zurück
*/
public function getProtocol(): ?string
{
return $this->requestContext->protocol;
}
/**
* Prüft, ob HTTPS verwendet wird
*/
public function isSecure(): bool
{
return $this->requestContext->protocol === 'https';
}
/**
* Gibt Headers zurück
*/
public function getHeaders(): array
{
return $this->requestContext->headers;
}
/**
* Prüft, ob Header existiert
*/
public function hasHeader(string $name): bool
{
return array_key_exists($name, $this->requestContext->headers);
}
/**
* Gibt spezifischen Header zurück
*/
public function getHeader(string $name): ?string
{
return $this->requestContext->headers[$name] ?? null;
}
/**
* Konvertiert zu Privacy-sicherem Array (für öffentliche Logs)
*/
public function toPrivacySafeArray(): array
{
return [
'method' => $this->getMethod(),
'uri_path' => $this->getUriPath(),
'protocol' => $this->getProtocol(),
'is_secure' => $this->isSecure(),
'user_agent_family' => $this->getUserAgentFamily(),
'request_id' => $this->getRequestId(),
];
}
/**
* Gibt nur URI-Path zurück (ohne Query-Parameter für Privacy)
*/
public function getUriPath(): ?string
{
$uri = $this->getUri();
if ($uri === null) {
return null;
}
return parse_url($uri, PHP_URL_PATH) ?: $uri;
}
/**
* Gibt User-Agent-Familie zurück (ohne Details für Privacy)
*/
public function getUserAgentFamily(): ?string
{
$userAgent = $this->getUserAgent();
if ($userAgent === null) {
return null;
}
// Einfache User-Agent-Familie-Erkennung
if (str_contains($userAgent, 'Chrome')) {
return 'Chrome';
}
if (str_contains($userAgent, 'Firefox')) {
return 'Firefox';
}
if (str_contains($userAgent, 'Safari')) {
return 'Safari';
}
if (str_contains($userAgent, 'Edge')) {
return 'Edge';
}
return 'Unknown';
}
/**
* Zugriff auf den ursprünglichen Framework RequestContext
*/
public function getFrameworkContext(): FrameworkRequestContext
{
return $this->requestContext;
}
}

View File

@@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging\ValueObjects;
/**
* User-Kontext Value Object für Structured Logging.
*
* Sammelt Benutzer-bezogene Informationen für Audit-Logs und
* Benutzer-spezifische Log-Analyse.
*/
final readonly class UserContext
{
public function __construct(
public ?string $userId = null,
public ?string $username = null,
public ?string $email = null,
public ?string $sessionId = null,
public array $roles = [],
public array $permissions = [],
public ?string $authMethod = null,
public bool $isAuthenticated = false,
public array $metadata = []
) {
}
/**
* Erstellt UserContext für authentifizierten Benutzer
*/
public static function authenticated(
string $userId,
?string $username = null,
?string $email = null,
?string $sessionId = null,
array $roles = [],
array $permissions = [],
string $authMethod = 'session'
): self {
return new self(
userId: $userId,
username: $username,
email: $email,
sessionId: $sessionId,
roles: $roles,
permissions: $permissions,
authMethod: $authMethod,
isAuthenticated: true
);
}
/**
* Erstellt UserContext für anonymen Benutzer
*/
public static function anonymous(?string $sessionId = null): self
{
return new self(
sessionId: $sessionId,
isAuthenticated: false
);
}
/**
* Erstellt UserContext aus Session-Daten
*/
public static function fromSession(array $sessionData): self
{
$userId = self::extractUserId($sessionData);
$isAuthenticated = $userId !== null;
return new self(
userId: $userId,
username: $sessionData['username'] ?? $sessionData['name'] ?? null,
email: $sessionData['email'] ?? null,
sessionId: $sessionData['session_id'] ?? null,
roles: $sessionData['roles'] ?? [],
permissions: $sessionData['permissions'] ?? [],
authMethod: $sessionData['auth_method'] ?? 'session',
isAuthenticated: $isAuthenticated,
metadata: array_diff_key($sessionData, array_flip([
'user_id', 'id', 'username', 'name', 'email', 'session_id',
'roles', 'permissions', 'auth_method',
]))
);
}
/**
* Fügt Rolle hinzu
*/
public function withRole(string $role): self
{
return new self(
userId: $this->userId,
username: $this->username,
email: $this->email,
sessionId: $this->sessionId,
roles: array_unique([...$this->roles, $role]),
permissions: $this->permissions,
authMethod: $this->authMethod,
isAuthenticated: $this->isAuthenticated,
metadata: $this->metadata
);
}
/**
* Fügt Permission hinzu
*/
public function withPermission(string $permission): self
{
return new self(
userId: $this->userId,
username: $this->username,
email: $this->email,
sessionId: $this->sessionId,
roles: $this->roles,
permissions: array_unique([...$this->permissions, $permission]),
authMethod: $this->authMethod,
isAuthenticated: $this->isAuthenticated,
metadata: $this->metadata
);
}
/**
* Fügt Metadaten hinzu
*/
public function withMetadata(string $key, mixed $value): self
{
return new self(
userId: $this->userId,
username: $this->username,
email: $this->email,
sessionId: $this->sessionId,
roles: $this->roles,
permissions: $this->permissions,
authMethod: $this->authMethod,
isAuthenticated: $this->isAuthenticated,
metadata: array_merge($this->metadata, [$key => $value])
);
}
/**
* Prüft, ob Benutzer bestimmte Rolle hat
*/
public function hasRole(string $role): bool
{
return in_array($role, $this->roles, true);
}
/**
* Prüft, ob Benutzer bestimmte Permission hat
*/
public function hasPermission(string $permission): bool
{
return in_array($permission, $this->permissions, true);
}
/**
* Gibt anonymisierten User-Identifier zurück (für Privacy)
*/
public function getAnonymizedId(): ?string
{
if ($this->userId === null) {
return null;
}
// Hash für anonyme Logs
return substr(hash('sha256', $this->userId), 0, 8);
}
/**
* Maskiert Email für Logs
*/
public function getMaskedEmail(): ?string
{
if ($this->email === null) {
return null;
}
$parts = explode('@', $this->email);
if (count($parts) !== 2) {
return '***@***';
}
$local = $parts[0];
$domain = $parts[1];
// Maskiere lokalen Teil
$maskedLocal = strlen($local) > 2
? substr($local, 0, 1) . str_repeat('*', strlen($local) - 2) . substr($local, -1)
: str_repeat('*', strlen($local));
return $maskedLocal . '@' . $domain;
}
/**
* Konvertiert zu Array für Serialisierung
*/
public function toArray(): array
{
$result = [
'user_id' => $this->userId,
'is_authenticated' => $this->isAuthenticated,
];
if ($this->username !== null) {
$result['username'] = $this->username;
}
if ($this->email !== null) {
$result['email_masked'] = $this->getMaskedEmail();
}
if ($this->sessionId !== null) {
$result['session_id'] = $this->sessionId;
}
if (! empty($this->roles)) {
$result['roles'] = $this->roles;
}
if (! empty($this->permissions)) {
$result['permissions'] = $this->permissions;
}
if ($this->authMethod !== null) {
$result['auth_method'] = $this->authMethod;
}
if (! empty($this->metadata)) {
$result['metadata'] = $this->metadata;
}
return $result;
}
/**
* Konvertiert zu Array für Privacy-sichere Logs
*/
public function toPrivacySafeArray(): array
{
return [
'user_id_anonymized' => $this->getAnonymizedId(),
'is_authenticated' => $this->isAuthenticated,
'roles_count' => count($this->roles),
'permissions_count' => count($this->permissions),
'auth_method' => $this->authMethod,
'has_session' => $this->sessionId !== null,
];
}
/**
* Extrahiert User-ID aus verschiedenen Session-Formaten
*/
private static function extractUserId(array $sessionData): ?string
{
$userIdKeys = ['user_id', 'id', 'user', 'auth_user_id', 'logged_in_user'];
foreach ($userIdKeys as $key) {
if (! isset($sessionData[$key])) {
continue;
}
$userId = $sessionData[$key];
// Handle user objects
if (is_object($userId) && isset($userId->id)) {
return (string) $userId->id;
}
// Handle arrays
if (is_array($userId) && isset($userId['id'])) {
return (string) $userId['id'];
}
// Handle simple values
if (is_scalar($userId)) {
return (string) $userId;
}
}
return null;
}
}