Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace App\Application\Http;
use App\Framework\Attributes\Route;
use App\Framework\Exception\FrameworkException;
use App\Framework\Http\Batch\BatchProcessor;
use App\Framework\Http\Batch\BatchRequest;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Status;
use App\Framework\Serialization\JsonSerializer;
use App\Framework\Serialization\JsonSerializerConfig;
/**
* Controller for handling batch API requests
*/
final readonly class BatchController
{
public function __construct(
private BatchProcessor $batchProcessor,
private JsonSerializer $jsonSerializer
) {
}
#[Route(path: '/api/batch', method: Method::POST)]
public function processBatch(Request $request): HttpResponse
{
try {
// Validate content type
$contentType = $request->headers->getFirst('Content-Type', '');
if (! str_contains($contentType, 'application/json')) {
return $this->errorResponse(
'Content-Type must be application/json',
Status::BAD_REQUEST
);
}
// Parse batch request
$batchData = $this->jsonSerializer->deserialize($request->body);
if (! is_array($batchData)) {
return $this->errorResponse(
'Invalid JSON structure',
Status::BAD_REQUEST
);
}
$batchRequest = BatchRequest::fromArray($batchData);
// Process batch operations
$responses = $this->batchProcessor->process($batchRequest);
// Return batch responses
$responseData = [
'responses' => array_map(fn ($response) => $response->toArray(), $responses),
'total' => count($responses),
'successful' => count(array_filter($responses, fn ($r) => $r->error === null)),
'failed' => count(array_filter($responses, fn ($r) => $r->error !== null)),
];
return new HttpResponse(
status: Status::OK,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize(
$responseData,
JsonSerializerConfig::pretty()
)
);
} catch (FrameworkException $e) {
return $this->errorResponse($e->getMessage(), Status::BAD_REQUEST);
} catch (\Throwable $e) {
return $this->errorResponse(
'Internal server error',
Status::INTERNAL_SERVER_ERROR
);
}
}
#[Route(path: '/api/batch/info', method: Method::GET)]
public function getBatchInfo(Request $request): HttpResponse
{
$info = [
'max_operations' => 100,
'max_concurrent_operations' => 10,
'supported_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
'continue_on_error_supported' => true,
'example_request' => [
'operations' => [
[
'id' => 'get-user-1',
'method' => 'GET',
'path' => '/api/users/1',
'headers' => ['Authorization' => 'Bearer token'],
],
[
'id' => 'create-post',
'method' => 'POST',
'path' => '/api/posts',
'headers' => ['Content-Type' => 'application/json'],
'body' => '{"title": "Test Post", "content": "Hello World"}',
],
],
'continue_on_error' => false,
],
];
return new HttpResponse(
status: Status::OK,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize(
$info,
JsonSerializerConfig::pretty()
)
);
}
private function errorResponse(string $message, Status $status): HttpResponse
{
return new HttpResponse(
status: $status,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize(['error' => $message])
);
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Application\Http\Controllers;
@@ -11,6 +12,7 @@ use App\Framework\Router\Result\WebSocketResult;
final class ChatController
{
/** @var array<string, WebSocketConnection> */
private array $connections = [];
#[Auth]
@@ -18,53 +20,56 @@ final class ChatController
public function chatWebSocket(): WebSocketResult
{
return new WebSocketResult()
->onConnect(function(WebSocketConnection $connection) {
->onConnect(function (WebSocketConnection $connection) {
$this->connections[$connection->getId()] = $connection;
// Willkommensnachricht senden
$connection->sendJson([
'type' => 'system',
'message' => 'Willkommen im Chat!',
'timestamp' => time()
'timestamp' => time(),
]);
// Andere Benutzer benachrichtigen
$this->broadcast([
'type' => 'user_joined',
'message' => 'Ein neuer Benutzer ist dem Chat beigetreten',
'timestamp' => time()
'timestamp' => time(),
], $connection->getId());
})
->onMessage(function(WebSocketConnection $connection, string $message) {
->onMessage(function (WebSocketConnection $connection, string $message) {
$data = json_decode($message, true);
if (!$data || !isset($data['type'])) {
if (! $data || ! isset($data['type'])) {
$connection->sendJson(['error' => 'Invalid message format']);
return;
}
switch ($data['type']) {
case 'chat_message':
$this->handleChatMessage($connection, $data);
break;
case 'ping':
$connection->sendJson(['type' => 'pong']);
break;
default:
$connection->sendJson(['error' => 'Unknown message type']);
}
})
->onClose(function(WebSocketConnection $connection, int $code, string $reason) {
->onClose(function (WebSocketConnection $connection, int $code, string $reason) {
unset($this->connections[$connection->getId()]);
// Andere Benutzer benachrichtigen
$this->broadcast([
'type' => 'user_left',
'message' => 'Ein Benutzer hat den Chat verlassen',
'timestamp' => time()
'timestamp' => time(),
]);
})
->onError(function(WebSocketConnection $connection, \Throwable $error) {
->onError(function (WebSocketConnection $connection, \Throwable $error) {
error_log("WebSocket error: " . $error->getMessage());
$connection->close(1011, 'Internal server error');
})
@@ -75,8 +80,9 @@ final class ChatController
private function handleChatMessage(WebSocketConnection $sender, array $data): void
{
if (!isset($data['message'])) {
if (! isset($data['message'])) {
$sender->sendJson(['error' => 'Message content required']);
return;
}
@@ -84,7 +90,7 @@ final class ChatController
'type' => 'chat_message',
'user_id' => $sender->getId(),
'message' => $data['message'],
'timestamp' => time()
'timestamp' => time(),
];
// Nachricht an alle Verbindungen senden

View File

@@ -1,10 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Application\Http\Controllers;
use App\Framework\Attributes\Route;
use App\Framework\Auth\Auth;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Timer;
use App\Framework\Http\Method;
use App\Framework\Http\SseStream;
use App\Framework\Http\Status;
@@ -16,6 +19,11 @@ use App\Framework\Router\Result\SseResultWithCallback;
*/
final class NotificationController
{
public function __construct(
private readonly Timer $timer
) {
}
/**
* Stellt einen SSE-Stream für allgemeine Benachrichtigungen bereit
*/
@@ -39,7 +47,7 @@ final class NotificationController
'type' => 'info',
'title' => 'Willkommen',
'message' => 'Sie sind jetzt mit Echtzeit-Updates verbunden.',
'timestamp' => time()
'timestamp' => time(),
],
'notification',
'notif-' . uniqid()
@@ -57,7 +65,7 @@ final class NotificationController
{
// SSE-Result mit benutzerdefinierten Headern
$result = new SseResult(Status::OK, 3000, [
'X-User-ID' => (string)$userId
'X-User-ID' => (string)$userId,
]);
// Verbindungsbestätigung mit Benutzer-ID
@@ -65,7 +73,7 @@ final class NotificationController
[
'message' => 'Verbunden mit dem Benutzer-Stream',
'userId' => $userId,
'timestamp' => time()
'timestamp' => time(),
],
'connected',
'conn-user-' . $userId
@@ -95,15 +103,15 @@ final class NotificationController
'type' => 'message',
'title' => 'Neue Nachricht',
'message' => 'Sie haben eine neue Nachricht erhalten',
'timestamp' => time() - 300 // Vor 5 Minuten
'timestamp' => time() - 300, // Vor 5 Minuten
],
[
'id' => 'notif-' . uniqid(),
'type' => 'system',
'title' => 'System-Update',
'message' => 'Das System wurde aktualisiert',
'timestamp' => time() - 3600 // Vor 1 Stunde
]
'timestamp' => time() - 3600, // Vor 1 Stunde
],
];
}
@@ -118,7 +126,7 @@ final class NotificationController
#$result = new SseResultWithCallback(Status::OK, 3000);
// Callback für dynamische Updates festlegen
$callback = function(SseStream $stream) {
$callback = function (SseStream $stream) {
// Simuliere neue Benachrichtigungen (mit 10% Wahrscheinlichkeit)
if (rand(1, 10) === 1) {
$notificationTypes = ['info', 'warning', 'update', 'message'];
@@ -129,13 +137,13 @@ final class NotificationController
'type' => $type,
'title' => 'Neue ' . ucfirst($type) . '-Benachrichtigung',
'message' => 'Dies ist eine dynamisch generierte Benachrichtigung vom Typ ' . $type,
'timestamp' => time()
'timestamp' => time(),
];
$stream->sendJson($notification, 'notification', $notification['id']);
// Kleine Pause nach dem Senden, um das Testszenario zu simulieren
sleep(1);
$this->timer->sleep(Duration::fromSeconds(1));
}
};

View File

@@ -4,92 +4,150 @@ declare(strict_types=1);
namespace App\Application\Http\Controllers;
use App\Application\Service\QrCodeService;
use App\Domain\QrCode\ValueObject\ErrorCorrectionLevel;
use App\Framework\Attributes\Route;
use App\Framework\Auth\Auth;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Http\Response;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Http\Status;
use App\Framework\QrCode\ErrorCorrectionLevel;
use App\Framework\QrCode\QrCodeGenerator;
use App\Framework\QrCode\QrCodeVersion;
final class QrCodeController
/**
* QR Code API Controller
*
* Provides API endpoints for QR code generation using the Framework QrCode module
*/
final readonly class QrCodeController
{
public function __construct(
private readonly QrCodeService $qrCodeService
private QrCodeGenerator $qrCodeGenerator
) {
}
/**
* Generiert einen QR-Code als SVG
* Generate QR code as SVG
*/
#[Auth]
#[Route(path: '/api/qrcode/svg', method: Method::GET)]
public function generateSvg(): Response
{
$data = $_GET['data'] ?? 'https://example.com';
$errorLevel = $this->getErrorLevel($_GET['error_level'] ?? 'M');
$moduleSize = (int) ($_GET['module_size'] ?? 4);
$margin = (int) ($_GET['margin'] ?? 4);
$foreground = $_GET['foreground'] ?? '#000000';
$background = $_GET['background'] ?? '#FFFFFF';
$errorLevel = $this->parseErrorLevel($_GET['error'] ?? 'M');
$version = $_GET['version'] ?? null;
$config = new QrCodeConfig($moduleSize, $margin, $foreground, $background);
try {
$qrVersion = $version ? new QrCodeVersion((int) $version) : null;
$svg = $this->qrCodeService->generateSvg($data, $errorLevel, $config);
$svg = $this->qrCodeGenerator->generateSvg($data, $errorLevel, $qrVersion);
return new Response(
body: $svg,
status: Status::OK,
headers: ['Content-Type' => 'image/svg+xml']
);
return new HttpResponse(
status : Status::OK,
headers: new Headers(['Content-Type' => 'image/svg+xml']),
body : $svg
);
} catch (\Exception $e) {
return new JsonResponse(
body : ['error' => $e->getMessage()],
status: Status::BAD_REQUEST
);
}
}
/**
* Generiert einen QR-Code als PNG
* Generate QR code as Data URI (base64 encoded SVG)
*/
#[Auth]
#[Route(path: '/api/qrcode/png', method: Method::GET)]
public function generatePng(): Response
#[Route(path: '/api/qrcode/datauri', method: Method::GET)]
public function generateDataUri(): Response
{
$data = $_GET['data'] ?? 'https://example.com';
$errorLevel = $this->getErrorLevel($_GET['error_level'] ?? 'M');
$moduleSize = (int) ($_GET['module_size'] ?? 4);
$margin = (int) ($_GET['margin'] ?? 4);
$errorLevel = $this->parseErrorLevel($_GET['error'] ?? 'M');
$version = $_GET['version'] ?? null;
$config = new QrCodeConfig($moduleSize, $margin);
try {
$qrVersion = $version ? new QrCodeVersion((int) $version) : null;
$png = $this->qrCodeService->generatePng($data, $errorLevel, $config);
$dataUri = $this->qrCodeGenerator->generateDataUri($data, $errorLevel, $qrVersion);
return new Response(
body: $png,
status: Status::OK,
headers: ['Content-Type' => 'image/png']
);
return new JsonResponse(
body : ['dataUri' => $dataUri]
);
} catch (\Exception $e) {
return new JsonResponse(
body : ['error' => $e->getMessage()],
status: Status::BAD_REQUEST
);
}
}
/**
* Generiert einen QR-Code als ASCII-Art
* Analyze data and get QR code recommendations
*/
#[Auth]
#[Route(path: '/api/qrcode/ascii', method: Method::GET)]
public function generateAscii(): Response
#[Route(path: '/api/qrcode/analyze', method: Method::GET)]
public function analyzeData(): Response
{
$data = $_GET['data'] ?? 'https://example.com';
$errorLevel = $this->getErrorLevel($_GET['error_level'] ?? 'M');
$data = $_GET['data'] ?? null;
$ascii = $this->qrCodeService->generateAscii($data, $errorLevel);
if (empty($data)) {
return new JsonResponse(
body : ['error' => 'Data parameter is required'],
status: Status::BAD_REQUEST
);
}
return new Response(
body: "<pre>$ascii</pre>",
status: Status::OK,
headers: ['Content-Type' => 'text/html; charset=utf-8']
);
try {
$analysis = $this->qrCodeGenerator->analyzeData($data);
return new JsonResponse(
body : $analysis,
status: Status::OK
);
} catch (\Exception $e) {
return new JsonResponse(
body : ['error' => $e->getMessage()],
status: Status::BAD_REQUEST
);
}
}
/**
* Konvertiert einen String in ein ErrorCorrectionLevel-Enum
* Generate TOTP QR code specifically optimized for authenticator apps
*/
private function getErrorLevel(string $level): ErrorCorrectionLevel
#[Route(path: '/api/qrcode/totp', method: Method::GET)]
public function generateTotpQrCode(): HttpResponse
{
$totpUri = $_GET['uri'] ?? null;
if (empty($totpUri)) {
return new HttpResponse(
status : Status::BAD_REQUEST,
headers: new Headers(['Content-Type' => 'application/json']),
body : json_encode(['error' => 'TOTP URI parameter is required'])
);
}
try {
$svg = $this->qrCodeGenerator->generateTotpQrCode($totpUri);
return new HttpResponse(
status : Status::OK,
headers: new Headers(['Content-Type' => 'image/svg+xml']),
body : $svg
);
} catch (\Exception $e) {
return new HttpResponse(
status : Status::BAD_REQUEST,
headers: new Headers(['Content-Type' => 'application/json']),
body : json_encode(['error' => $e->getMessage()])
);
}
}
/**
* Parse error correction level from string
*/
private function parseErrorLevel(string $level): ErrorCorrectionLevel
{
return match (strtoupper($level)) {
'L' => ErrorCorrectionLevel::L,

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Application\Http\Examples;
use App\Framework\Attributes\Route;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Status;
use App\Framework\Serialization\JsonSerializer;
use App\Framework\Serialization\JsonSerializerConfig;
/**
* Example controller for demonstrating batch API functionality
*/
final readonly class BatchExampleController
{
public function __construct(
private JsonSerializer $jsonSerializer
) {
}
#[Route(path: '/api/examples/users/{id}', method: Method::GET)]
public function getUser(Request $request): HttpResponse
{
$userId = $request->queryParams['id'] ?? '1';
$user = [
'id' => (int) $userId,
'name' => "User {$userId}",
'email' => "user{$userId}@example.com",
'created_at' => date('Y-m-d H:i:s'),
];
return new HttpResponse(
status: Status::OK,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize($user, JsonSerializerConfig::pretty())
);
}
#[Route(path: '/api/examples/posts', method: Method::POST)]
public function createPost(Request $request): HttpResponse
{
$data = $this->jsonSerializer->deserialize($request->body);
if (! is_array($data) || empty($data['title'])) {
return new HttpResponse(
status: Status::BAD_REQUEST,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize(['error' => 'Title is required'])
);
}
$post = [
'id' => random_int(1000, 9999),
'title' => $data['title'],
'content' => $data['content'] ?? '',
'author_id' => $data['author_id'] ?? 1,
'created_at' => date('Y-m-d H:i:s'),
];
return new HttpResponse(
status: Status::CREATED,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize($post, JsonSerializerConfig::pretty())
);
}
#[Route(path: '/api/examples/posts/{id}', method: Method::PUT)]
public function updatePost(Request $request): HttpResponse
{
$postId = $request->queryParams['id'] ?? '1';
$data = $this->jsonSerializer->deserialize($request->body);
if (! is_array($data)) {
return new HttpResponse(
status: Status::BAD_REQUEST,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize(['error' => 'Invalid JSON data'])
);
}
$post = [
'id' => (int) $postId,
'title' => $data['title'] ?? "Post {$postId}",
'content' => $data['content'] ?? '',
'updated_at' => date('Y-m-d H:i:s'),
];
return new HttpResponse(
status: Status::OK,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize($post, JsonSerializerConfig::pretty())
);
}
#[Route(path: '/api/examples/posts/{id}', method: Method::DELETE)]
public function deletePost(Request $request): HttpResponse
{
$postId = $request->queryParams['id'] ?? '1';
return new HttpResponse(
status: Status::NO_CONTENT,
headers: new Headers([]),
body: ''
);
}
#[Route(path: '/api/examples/slow', method: Method::GET)]
public function slowEndpoint(Request $request): HttpResponse
{
// Simulate slow operation for testing concurrent processing
$delay = (int) ($request->queryParams['delay'] ?? 1);
$maxDelay = min($delay, 5); // Maximum 5 seconds
sleep($maxDelay);
return new HttpResponse(
status: Status::OK,
headers: new Headers(['Content-Type' => 'application/json']),
body: $this->jsonSerializer->serialize([
'message' => 'Slow operation completed',
'delay' => $maxDelay,
'timestamp' => date('Y-m-d H:i:s'),
])
);
}
}

View File

@@ -1,17 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Application\Http;
use App\Framework\Attributes\Route;
use App\Framework\CommandBus\CommandBus;
use App\Framework\Http\HeaderKey;
use App\Framework\Http\Request;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Status;
use App\Framework\Router\ActionResult;
use App\Framework\Router\Result\ViewResult;
use App\Framework\Router\Result\Redirect;
use App\Framework\Meta\MetaData;
use App\Framework\Router\ActionResult;
use App\Framework\Router\Result\Redirect;
use App\Framework\Router\Result\ViewResult;
use App\Framework\Smartlinks\Actions\ActionRegistry;
use App\Framework\Smartlinks\Commands\ExecuteSmartlinkCommand;
use App\Framework\Smartlinks\Commands\GenerateSmartlinkCommand;
@@ -27,7 +29,8 @@ final readonly class Smartlink
private ActionRegistry $actionRegistry,
private SmartlinkService $smartlinkService,
private GenerateSmartlinkHandler $handler,
) {}
) {
}
#[Route('/smartlink/{token}', method: Method::GET)]
#[Route('/smartlink/{token}', method: Method::POST)]
@@ -46,7 +49,7 @@ final readonly class Smartlink
// Token validieren
$smartlinkData = $this->smartlinkService->validate($smartlinkToken);
if (!$smartlinkData) {
if (! $smartlinkData) {
return new ViewResult(
template: 'smartlinks-error',
metaData: new MetaData(
@@ -55,7 +58,7 @@ final readonly class Smartlink
),
data: [
'error' => 'Ungültiger oder abgelaufener Link',
'error_code' => 'INVALID_TOKEN'
'error_code' => 'INVALID_TOKEN',
],
status: Status::NOT_FOUND
);
@@ -63,7 +66,7 @@ final readonly class Smartlink
// Action holen
$action = $this->actionRegistry->get($smartlinkData->action);
if (!$action) {
if (! $action) {
return new ViewResult(
template: 'smartlinks-error',
metaData: new MetaData(
@@ -72,7 +75,7 @@ final readonly class Smartlink
),
data: [
'error' => 'Unbekannte Aktion',
'error_code' => 'UNKNOWN_ACTION'
'error_code' => 'UNKNOWN_ACTION',
],
status: Status::BAD_REQUEST
);
@@ -85,7 +88,7 @@ final readonly class Smartlink
'query_params' => $request->queryParameters ?? [],
#'ip_address' => $request->serverEnvironment->ipAddress?->value,
'user_agent' => $request->headers->getFirst(HeaderKey::USER_AGENT),
'headers' => $request->headers
'headers' => $request->headers,
];
// Command ausführen
@@ -93,7 +96,7 @@ final readonly class Smartlink
$result = $this->commandBus->dispatch($command);
// Ergebnis verarbeiten
if (!$result->isSuccess()) {
if (! $result->isSuccess()) {
return new ViewResult(
template: $action->getErrorTemplate(),
metaData: new MetaData(
@@ -103,7 +106,7 @@ final readonly class Smartlink
data: [
'error' => $result->message,
'errors' => $result->errors,
'action' => $action->getName()
'action' => $action->getName(),
],
status: Status::BAD_REQUEST
);
@@ -123,7 +126,7 @@ final readonly class Smartlink
data: [
'result' => $result,
'action' => $action->getName(),
'token' => $token
'token' => $token,
]
);
@@ -137,7 +140,7 @@ final readonly class Smartlink
data: [
'error' => 'Ein Fehler ist aufgetreten',
'error_code' => 'SYSTEM_ERROR',
'debug_message' => $e->getMessage() // Nur für Development
'debug_message' => $e->getMessage(), // Nur für Development
],
status: Status::INTERNAL_SERVER_ERROR
);