fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,441 @@
<?php
declare(strict_types=1);
namespace App\Framework\ExceptionHandling\ValueObjects;
/**
* Stack Item Value Object für strukturierte Stack Trace Darstellung
*
* Repräsentiert einen einzelnen Frame im Stack Trace mit formatierter Ausgabe.
* Entfernt Namespaces aus Klassennamen für bessere Lesbarkeit.
*/
final readonly class StackItem
{
public function __construct(
public string $file,
public int $line,
public ?string $function = null,
public ?string $class = null,
public ?string $type = null,
public array $args = []
) {
}
/**
* Erstellt StackItem aus debug_backtrace Array
*
* @param array<string, mixed> $frame
*/
public static function fromArray(array $frame): self
{
// Bereinige Args, um nicht-serialisierbare Objekte zu entfernen
$args = isset($frame['args']) ? self::sanitizeArgs($frame['args']) : [];
// Normalisiere Klassenname: Forward-Slashes zu Backslashes
$class = null;
if (isset($frame['class']) && is_string($frame['class'])) {
$class = str_replace('/', '\\', $frame['class']);
}
return new self(
file: $frame['file'] ?? 'unknown',
line: $frame['line'] ?? 0,
function: $frame['function'] ?? null,
class: $class,
type: $frame['type'] ?? null,
args: $args
);
}
/**
* Bereinigt Args, um nicht-serialisierbare Objekte (wie ReflectionClass) zu entfernen
*
* @param array<int, mixed> $args
* @return array<int, mixed>
*/
private static function sanitizeArgs(array $args): array
{
return array_map(
fn($arg) => self::sanitizeValue($arg),
$args
);
}
/**
* Bereinigt einzelnen Wert, entfernt nicht-serialisierbare Objekte
*/
private static function sanitizeValue(mixed $value): mixed
{
// Closures können nicht serialisiert werden
if ($value instanceof \Closure) {
try {
$reflection = new \ReflectionFunction($value);
$file = $reflection->getFileName();
$line = $reflection->getStartLine();
return sprintf('Closure(%s:%d)', basename($file), $line);
} catch (\Throwable) {
return 'Closure';
}
}
// Reflection-Objekte können nicht serialisiert werden
if (is_object($value)) {
$className = get_class($value);
if ($value instanceof \ReflectionClass
|| $value instanceof \ReflectionMethod
|| $value instanceof \ReflectionProperty
|| $value instanceof \ReflectionFunction
|| $value instanceof \ReflectionParameter
|| $value instanceof \ReflectionType
|| str_starts_with($className, 'Reflection')) {
return sprintf('ReflectionObject(%s)', $className);
}
// Anonyme Klassen können auch Probleme verursachen
if (str_contains($className, '@anonymous')) {
$parentClass = get_parent_class($value);
if ($parentClass !== false) {
return sprintf('Anonymous(%s)', $parentClass);
}
return 'Anonymous';
}
// Andere Objekte durch Klassenname ersetzen
return $className;
}
// Arrays rekursiv bereinigen
if (is_array($value)) {
return array_map(
fn($item) => self::sanitizeValue($item),
$value
);
}
// Primitives bleiben unverändert
return $value;
}
/**
* Gibt Klasse ohne Namespace zurück
* Behandelt anonyme Klassen, indem das Interface/Parent-Class extrahiert wird
*/
public function getShortClass(): ?string
{
if ($this->class === null) {
return null;
}
// Normalisiere Forward-Slashes zu Backslashes (falls vorhanden)
// Dies ist wichtig, da manche Systeme Forward-Slashes verwenden
$normalizedClass = str_replace('/', '\\', $this->class);
// Anonyme Klassen erkennen: Format ist z.B. "App\Framework\Http\Next@anonymous/path/to/file.php:line$hash"
// oder "Next@anonymous/path/to/file.php:line$hash"
if (str_contains($normalizedClass, '@anonymous')) {
// Extrahiere den Teil vor @anonymous (normalerweise das Interface mit vollständigem Namespace)
$match = preg_match('/^([^@]+)@anonymous/', $normalizedClass, $matches);
if ($match && isset($matches[1])) {
$interfaceName = $matches[1];
// Entferne Namespace: Spalte am Backslash und nimm den letzten Teil
$parts = explode('\\', $interfaceName);
$shortName = end($parts);
return $shortName . ' (anonymous)';
}
return 'Anonymous';
}
// Spalte am Backslash und nimm den letzten Teil (Klassenname ohne Namespace)
$parts = explode('\\', $normalizedClass);
$shortName = end($parts);
// Sicherstellen, dass wir wirklich nur den letzten Teil zurückgeben
// (falls explode() nicht funktioniert hat, z.B. bei Forward-Slashes)
if ($shortName === $normalizedClass && str_contains($normalizedClass, '/')) {
// Fallback: versuche es mit Forward-Slash
$parts = explode('/', $normalizedClass);
$shortName = end($parts);
}
return $shortName;
}
/**
* Gibt Kurzform des File-Pfads zurück (relativ zum Project Root wenn möglich)
*/
public function getShortFile(): string
{
$projectRoot = dirname(__DIR__, 4); // Von src/Framework/ExceptionHandling/ValueObjects nach root
if (str_starts_with($this->file, $projectRoot)) {
return substr($this->file, strlen($projectRoot) + 1);
}
return $this->file;
}
/**
* Gibt vollständigen Method/Function Call zurück (ohne Namespace)
*/
public function getCall(): string
{
$parts = [];
if ($this->class !== null) {
$parts[] = $this->getShortClass();
}
if ($this->type !== null) {
$parts[] = $this->type;
}
if ($this->function !== null) {
$parts[] = $this->function . '()';
}
return implode('', $parts);
}
/**
* Formatiert Parameter für Display (kompakte Darstellung)
*/
public function formatParameters(): string
{
if (empty($this->args)) {
return '';
}
$formatted = [];
foreach ($this->args as $arg) {
$formatted[] = $this->formatParameterForDisplay($arg);
}
return implode(', ', $formatted);
}
/**
* Formatiert einzelnen Parameter für Display
*/
private function formatParameterForDisplay(mixed $value): string
{
return match (true) {
is_string($value) => $this->formatStringParameter($value),
is_int($value), is_float($value) => (string) $value,
is_bool($value) => $value ? 'true' : 'false',
is_null($value) => 'null',
is_array($value) => sprintf('array(%d)', count($value)),
is_resource($value) => sprintf('resource(%s)', get_resource_type($value)),
is_object($value) => $this->formatObjectForDisplay($value),
default => get_debug_type($value),
};
}
/**
* Formatiert String-Parameter, entfernt Namespaces aus Klassennamen
*/
private function formatStringParameter(string $value): string
{
// Normalisiere Forward-Slashes zu Backslashes (falls vorhanden)
$normalizedValue = str_replace('/', '\\', $value);
// Wenn der String ein Klassename ist (vollständiger Namespace), entferne Namespace
if (class_exists($normalizedValue) || interface_exists($normalizedValue) || enum_exists($normalizedValue)) {
$parts = explode('\\', $normalizedValue);
$shortName = end($parts);
return sprintf("'%s'", $shortName);
}
// Prüfe, ob der String ein Namespace-Format hat (z.B. "App\Framework\Performance\PerformanceCategory")
// Auch wenn die Klasse nicht existiert, entferne Namespace
// Pattern: Beginnt mit Großbuchstaben, enthält Backslashes oder Forward-Slashes, endet mit Klassennamen
if (preg_match('/^[A-Z][a-zA-Z0-9_\\\\\/]+$/', $normalizedValue) && (str_contains($normalizedValue, '\\') || str_contains($value, '/'))) {
$parts = explode('\\', $normalizedValue);
// Nur wenn es mehrere Teile gibt (Namespace vorhanden)
if (count($parts) > 1) {
$shortName = end($parts);
return sprintf("'%s'", $shortName);
}
}
// Closure-String-Format: "Closure(RouteDispatcher.php:77)" oder "Closure(/full/path/RouteDispatcher.php:77)"
if (preg_match('/^Closure\(([^:]+):(\d+)\)$/', $value, $matches)) {
$file = basename($matches[1]);
$line = $matches[2];
return sprintf("Closure(%s:%s)", $file, $line);
}
// Lange Strings kürzen
if (strlen($value) > 50) {
return sprintf("'%s...'", substr($value, 0, 50));
}
return sprintf("'%s'", $value);
}
/**
* Formatiert Objekt für Display (kompakte Darstellung)
*/
private function formatObjectForDisplay(object $value): string
{
$className = get_class($value);
// Entferne Namespace für bessere Lesbarkeit
$parts = explode('\\', $className);
$shortName = end($parts);
// Anonyme Klassen
if (str_contains($className, '@anonymous')) {
$match = preg_match('/^([^@]+)@anonymous/', $className, $matches);
if ($match && isset($matches[1])) {
$interfaceName = $matches[1];
$interfaceParts = explode('\\', $interfaceName);
$shortInterface = end($interfaceParts);
return $shortInterface . ' (anonymous)';
}
return 'Anonymous';
}
return $shortName;
}
/**
* Formatiert Funktionsnamen, entfernt Namespaces und verbessert Closure-Darstellung
*/
private function formatFunctionName(?string $function): string
{
if ($function === null) {
return '';
}
// Closures haben Format: {closure:Namespace\Class::method():line} oder {closure:Namespace/Class::method():line}
if (preg_match('/\{closure:([^}]+)\}/', $function, $matches)) {
$closureInfo = $matches[1];
// Normalisiere Forward-Slashes zu Backslashes
$closureInfo = str_replace('/', '\\', $closureInfo);
// Parse: App\Framework\Router\RouteDispatcher::executeController():77
if (preg_match('/^([^:]+)::([^(]+)\(\):(\d+)$/', $closureInfo, $closureMatches)) {
$fullClass = $closureMatches[1];
$method = $closureMatches[2];
$line = $closureMatches[3];
// Entferne Namespace
$classParts = explode('\\', $fullClass);
$shortClass = end($classParts);
return sprintf('{closure:%s::%s():%s}', $shortClass, $method, $line);
}
// Fallback: einfach Namespaces entfernen
$closureInfo = preg_replace_callback(
'/([A-Z][a-zA-Z0-9_\\\\]*)/',
fn($m) => $this->removeNamespaceFromClass($m[0]),
$closureInfo
);
return sprintf('{closure:%s}', $closureInfo);
}
return $function;
}
/**
* Entfernt Namespace aus Klassennamen in Strings
*/
private function removeNamespaceFromClass(string $classString): string
{
// Normalisiere Forward-Slashes zu Backslashes
$normalized = str_replace('/', '\\', $classString);
$parts = explode('\\', $normalized);
return end($parts);
}
/**
* Formatiert für Display (HTML/Console)
* Verwendet Standard PHP Stack Trace Format: ClassName->methodName($param1, $param2, ...) in file.php:line
*/
public function formatForDisplay(): string
{
$shortFile = $this->getShortFile();
$location = sprintf('%s:%d', $shortFile, $this->line);
$params = $this->formatParameters();
// Wenn Klasse vorhanden
if ($this->class !== null && $this->function !== null) {
$className = $this->getShortClass();
$methodName = $this->formatFunctionName($this->function);
$separator = $this->type === '::' ? '::' : '->';
$paramsStr = $params !== '' ? $params : '';
return sprintf('%s%s%s(%s) in %s', $className, $separator, $methodName, $paramsStr, $location);
}
// Wenn nur Funktion vorhanden
if ($this->function !== null) {
$methodName = $this->formatFunctionName($this->function);
$paramsStr = $params !== '' ? $params : '';
return sprintf('%s(%s) in %s', $methodName, $paramsStr, $location);
}
// Wenn weder Klasse noch Funktion
return $location;
}
/**
* Konvertiert zu Array für JSON-Serialisierung
*
* @return array<string, mixed>
*/
public function toArray(): array
{
$data = [
'file' => $this->getShortFile(),
'full_file' => $this->file,
'line' => $this->line,
];
if ($this->function !== null) {
$data['function'] = $this->function;
}
if ($this->class !== null) {
$data['class'] = $this->getShortClass();
$data['full_class'] = $this->class;
}
if ($this->type !== null) {
$data['type'] = $this->type;
}
if (!empty($this->args)) {
$data['args'] = $this->serializeArgs();
}
return $data;
}
/**
* Serialisiert Arguments für Ausgabe
*
* @return array<int, mixed>
*/
private function serializeArgs(): array
{
return array_map(
fn($arg) => $this->formatValueForOutput($arg),
$this->args
);
}
/**
* Formatiert Wert für Ausgabe (kompaktere Darstellung)
*/
private function formatValueForOutput(mixed $value): mixed
{
return match (true) {
is_array($value) => sprintf('array(%d)', count($value)),
is_resource($value) => sprintf('resource(%s)', get_resource_type($value)),
is_string($value) && strlen($value) > 100 => substr($value, 0, 100) . '...',
default => $value,
};
}
}