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,312 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Assets;
use App\Framework\Async\FiberManager;
use App\Framework\Filesystem\Storage;
/**
* Asynchroner Asset-Processor für CSS, JS und Bilder
*/
final class AsyncAssetProcessor
{
public function __construct(
private readonly Storage $storage,
private readonly FiberManager $fiberManager = new FiberManager(),
private readonly array $config = []
) {
}
/**
* Verarbeitet alle Assets parallel
*/
public function processAll(string $sourceDir, string $outputDir): array
{
$operations = [
'css' => fn () => $this->processCss($sourceDir . '/css', $outputDir . '/css'),
'js' => fn () => $this->processJs($sourceDir . '/js', $outputDir . '/js'),
'images' => fn () => $this->processImages($sourceDir . '/images', $outputDir . '/images'),
];
return $this->fiberManager->batch($operations);
}
/**
* Verarbeitet CSS-Dateien
*/
public function processCss(string $sourceDir, string $outputDir): array
{
$cssFiles = $this->findFiles($sourceDir, '*.css');
$scssFiles = $this->findFiles($sourceDir, '*.scss');
$operations = [];
// Verarbeite CSS-Dateien
foreach ($cssFiles as $file) {
$operations["css_{$file}"] = fn () => $this->minifyCss($sourceDir . '/' . $file, $outputDir);
}
// Verarbeite SCSS-Dateien
foreach ($scssFiles as $file) {
$operations["scss_{$file}"] = fn () => $this->compileSass($sourceDir . '/' . $file, $outputDir);
}
return $this->fiberManager->batch($operations);
}
/**
* Verarbeitet JavaScript-Dateien
*/
public function processJs(string $sourceDir, string $outputDir): array
{
$jsFiles = $this->findFiles($sourceDir, '*.js');
$operations = [];
foreach ($jsFiles as $file) {
$operations["js_{$file}"] = fn () => $this->minifyJs($sourceDir . '/' . $file, $outputDir);
}
return $this->fiberManager->batch($operations);
}
/**
* Verarbeitet Bilder
*/
public function processImages(string $sourceDir, string $outputDir): array
{
$imageFiles = array_merge(
$this->findFiles($sourceDir, '*.jpg'),
$this->findFiles($sourceDir, '*.jpeg'),
$this->findFiles($sourceDir, '*.png'),
$this->findFiles($sourceDir, '*.gif'),
$this->findFiles($sourceDir, '*.svg')
);
$operations = [];
foreach ($imageFiles as $file) {
$operations["img_{$file}"] = fn () => $this->optimizeImage($sourceDir . '/' . $file, $outputDir);
}
return $this->fiberManager->batch($operations);
}
/**
* Bündelt JavaScript-Dateien
*/
public function bundleJs(array $files, string $outputFile): string
{
$operations = [];
foreach ($files as $file) {
$operations[$file] = fn () => $this->storage->get($file);
}
$contents = $this->fiberManager->batch($operations);
$bundled = implode("\n\n", array_filter($contents, fn ($c) => ! ($c instanceof \Throwable)));
$minified = $this->minifyJsContent($bundled);
$this->storage->put($outputFile, $minified);
return $outputFile;
}
/**
* Bündelt CSS-Dateien
*/
public function bundleCss(array $files, string $outputFile): string
{
$operations = [];
foreach ($files as $file) {
$operations[$file] = fn () => $this->storage->get($file);
}
$contents = $this->fiberManager->batch($operations);
$bundled = implode("\n\n", array_filter($contents, fn ($c) => ! ($c instanceof \Throwable)));
$minified = $this->minifyCssContent($bundled);
$this->storage->put($outputFile, $minified);
return $outputFile;
}
/**
* Generiert verschiedene Bildgrößen parallel
*/
public function generateImageSizes(string $sourceImage, array $sizes, string $outputDir): array
{
$operations = [];
foreach ($sizes as $sizeName => $dimensions) {
$operations[$sizeName] = fn () => $this->resizeImage(
$sourceImage,
$outputDir . '/' . $sizeName . '_' . basename($sourceImage),
$dimensions['width'],
$dimensions['height']
);
}
return $this->fiberManager->batch($operations);
}
private function minifyCss(string $sourceFile, string $outputDir): string
{
$content = $this->storage->get($sourceFile);
$minified = $this->minifyCssContent($content);
$outputFile = $outputDir . '/' . basename($sourceFile);
$this->storage->put($outputFile, $minified);
return $outputFile;
}
private function minifyCssContent(string $content): string
{
// Vereinfachte CSS-Minifizierung
$content = preg_replace('/\s+/', ' ', $content);
$content = str_replace(['; ', ' {', '{ ', ' }', '} ', ': '], [';', '{', '{', '}', '}', ':'], $content);
$content = preg_replace('/\/\*.*?\*\//', '', $content);
return trim($content);
}
private function compileSass(string $sourceFile, string $outputDir): string
{
// Vereinfachte SCSS-Kompilierung
// In Produktion würde man eine echte SCSS-Library verwenden
$content = $this->storage->get($sourceFile);
// Basis-Variable-Ersetzung
$content = $this->processSassVariables($content);
$minified = $this->minifyCssContent($content);
$outputFile = $outputDir . '/' . str_replace('.scss', '.css', basename($sourceFile));
$this->storage->put($outputFile, $minified);
return $outputFile;
}
private function minifyJs(string $sourceFile, string $outputDir): string
{
$content = $this->storage->get($sourceFile);
$minified = $this->minifyJsContent($content);
$outputFile = $outputDir . '/' . basename($sourceFile);
$this->storage->put($outputFile, $minified);
return $outputFile;
}
private function minifyJsContent(string $content): string
{
// Vereinfachte JS-Minifizierung
$content = preg_replace('/\s+/', ' ', $content);
$content = str_replace(['; ', ' {', '{ ', ' }', '} '], [';', '{', '{', '}', '}'], $content);
$content = preg_replace('/\/\*.*?\*\//', '', $content);
$content = preg_replace('/\/\/.*$/m', '', $content);
return trim($content);
}
private function optimizeImage(string $sourceFile, string $outputDir): string
{
// Vereinfachte Bildoptimierung
// In Produktion würde man Bibliotheken wie Imagick verwenden
$outputFile = $outputDir . '/' . basename($sourceFile);
// Kopiere erstmal nur
$content = $this->storage->get($sourceFile);
$this->storage->put($outputFile, $content);
return $outputFile;
}
private function resizeImage(string $sourceFile, string $outputFile, int $width, int $height): string
{
// Vereinfachte Bildgrößenänderung
// In Produktion würde man GD oder Imagick verwenden
$content = $this->storage->get($sourceFile);
$this->storage->put($outputFile, $content);
return $outputFile;
}
private function processSassVariables(string $content): string
{
// Vereinfachte Variable-Verarbeitung
preg_match_all('/\$([a-zA-Z0-9_-]+)\s*:\s*([^;]+);/', $content, $matches);
$variables = [];
for ($i = 0; $i < count($matches[0]); $i++) {
$variables['$' . $matches[1][$i]] = $matches[2][$i];
}
foreach ($variables as $var => $value) {
$content = str_replace($var, $value, $content);
}
// Entferne Variable-Definitionen
$content = preg_replace('/\$[a-zA-Z0-9_-]+\s*:\s*[^;]+;\s*/', '', $content);
return $content;
}
private function findFiles(string $directory, string $pattern): array
{
if (! is_dir($directory)) {
return [];
}
$files = glob($directory . '/' . $pattern);
return array_map('basename', $files ?: []);
}
/**
* Überwacht Dateien auf Änderungen und verarbeitet sie automatisch
*/
public function watch(string $sourceDir, string $outputDir): void
{
$this->fiberManager->async(function () use ($sourceDir, $outputDir) {
$lastCheck = [];
while (true) {
$files = array_merge(
glob($sourceDir . '/**/*.css') ?: [],
glob($sourceDir . '/**/*.scss') ?: [],
glob($sourceDir . '/**/*.js') ?: []
);
foreach ($files as $file) {
$mtime = filemtime($file);
if (! isset($lastCheck[$file]) || $lastCheck[$file] !== $mtime) {
$this->processFile($file, $sourceDir, $outputDir);
$lastCheck[$file] = $mtime;
}
}
sleep(1); // Prüfe jede Sekunde
}
});
}
private function processFile(string $file, string $sourceDir, string $outputDir): void
{
$extension = pathinfo($file, PATHINFO_EXTENSION);
$relativePath = str_replace($sourceDir . '/', '', $file);
switch ($extension) {
case 'css':
$this->minifyCss($file, dirname($outputDir . '/' . $relativePath));
break;
case 'scss':
$this->compileSass($file, dirname($outputDir . '/' . $relativePath));
break;
case 'js':
$this->minifyJs($file, dirname($outputDir . '/' . $relativePath));
break;
}
}
}

View File

@@ -0,0 +1,263 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Cache;
use App\Framework\Async\FiberManager;
/**
* Asynchrones Cache System
*/
final class AsyncCache
{
private array $memoryCache = [];
private string $cacheDir;
public function __construct(
?string $cacheDir = null,
private readonly FiberManager $fiberManager = new FiberManager(),
private readonly int $defaultTtl = 3600
) {
$this->cacheDir = $cacheDir ?? sys_get_temp_dir() . '/framework-cache';
if (! is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
/**
* Holt einen Wert aus dem Cache
*/
public function get(string $key, mixed $default = null): mixed
{
// Prüfe Memory Cache zuerst
if (isset($this->memoryCache[$key])) {
$item = $this->memoryCache[$key];
if ($item['expires'] === 0 || $item['expires'] > time()) {
return $item['value'];
}
unset($this->memoryCache[$key]);
}
// Prüfe File Cache
$filePath = $this->getFilePath($key);
if (is_file($filePath)) {
$data = @file_get_contents($filePath);
if ($data !== false) {
$item = @unserialize($data);
if ($item && ($item['expires'] === 0 || $item['expires'] > time())) {
// Lade in Memory Cache
$this->memoryCache[$key] = $item;
return $item['value'];
}
@unlink($filePath);
}
}
return $default;
}
/**
* Speichert einen Wert im Cache
*/
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
$ttl ??= $this->defaultTtl;
$expires = $ttl > 0 ? time() + $ttl : 0;
$item = [
'value' => $value,
'expires' => $expires,
'created' => time(),
];
// Speichere in Memory Cache
$this->memoryCache[$key] = $item;
// Speichere in File Cache (asynchron)
$this->fiberManager->async(function () use ($key, $item) {
$filePath = $this->getFilePath($key);
$data = serialize($item);
@file_put_contents($filePath, $data, LOCK_EX);
});
return true;
}
/**
* Holt mehrere Werte parallel
*/
public function getMultiple(array $keys): array
{
$operations = [];
foreach ($keys as $key) {
$operations[$key] = fn () => $this->get($key);
}
return $this->fiberManager->batch($operations);
}
/**
* Speichert mehrere Werte parallel
*/
public function setMultiple(array $items, ?int $ttl = null): bool
{
$operations = [];
foreach ($items as $key => $value) {
$operations[$key] = fn () => $this->set($key, $value, $ttl);
}
$results = $this->fiberManager->batch($operations);
// Prüfe ob alle erfolgreich waren
foreach ($results as $result) {
if ($result !== true) {
return false;
}
}
return true;
}
/**
* Löscht einen Cache-Eintrag
*/
public function delete(string $key): bool
{
unset($this->memoryCache[$key]);
$filePath = $this->getFilePath($key);
if (is_file($filePath)) {
return @unlink($filePath);
}
return true;
}
/**
* Löscht mehrere Cache-Einträge parallel
*/
public function deleteMultiple(array $keys): bool
{
$operations = [];
foreach ($keys as $key) {
$operations[$key] = fn () => $this->delete($key);
}
$results = $this->fiberManager->batch($operations);
foreach ($results as $result) {
if ($result !== true) {
return false;
}
}
return true;
}
/**
* Prüft ob ein Key existiert
*/
public function has(string $key): bool
{
return $this->get($key) !== null;
}
/**
* Leert den gesamten Cache
*/
public function clear(): bool
{
$this->memoryCache = [];
$files = glob($this->cacheDir . '/*.cache');
if ($files) {
$operations = [];
foreach ($files as $file) {
$operations[] = fn () => @unlink($file);
}
$this->fiberManager->batch($operations);
}
return true;
}
/**
* Holt oder erstellt einen Cache-Wert
*/
public function remember(string $key, callable $callback, ?int $ttl = null): mixed
{
$value = $this->get($key);
if ($value === null) {
$value = $callback();
$this->set($key, $value, $ttl);
}
return $value;
}
/**
* Erstellt mehrere Cache-Werte parallel falls sie nicht existieren
*/
public function rememberMultiple(array $callbacks, ?int $ttl = null): array
{
$results = [];
$toCreate = [];
// Prüfe welche Keys bereits existieren
foreach ($callbacks as $key => $callback) {
$value = $this->get($key);
if ($value !== null) {
$results[$key] = $value;
} else {
$toCreate[$key] = $callback;
}
}
// Erstelle fehlende Werte parallel
if (! empty($toCreate)) {
$operations = [];
foreach ($toCreate as $key => $callback) {
$operations[$key] = $callback;
}
$newValues = $this->fiberManager->batch($operations);
// Speichere neue Werte im Cache
$setOperations = [];
foreach ($newValues as $key => $value) {
$setOperations[$key] = fn () => $this->set($key, $value, $ttl);
$results[$key] = $value;
}
$this->fiberManager->batch($setOperations);
}
return $results;
}
private function getFilePath(string $key): string
{
$hash = hash('sha256', $key);
return $this->cacheDir . '/' . $hash . '.cache';
}
/**
* Gibt Cache-Statistiken zurück
*/
public function getStats(): array
{
$fileCount = count(glob($this->cacheDir . '/*.cache') ?: []);
return [
'memory_entries' => count($this->memoryCache),
'file_entries' => $fileCount,
'cache_dir' => $this->cacheDir,
];
}
}

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Database;
use App\Framework\Async\AsyncPool;
use App\Framework\Async\FiberManager;
use PDO;
/**
* Asynchroner Database Layer
*/
final class AsyncDatabase
{
private PDO $connection;
public function __construct(
string $dsn,
string $username = '',
string $password = '',
array $options = [],
private readonly FiberManager $fiberManager = new FiberManager()
) {
$defaultOptions = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$this->connection = new PDO($dsn, $username, $password, array_merge($defaultOptions, $options));
}
/**
* Führt eine Query aus
*/
public function query(string $sql, array $params = []): DatabaseResult
{
$startTime = microtime(true);
$stmt = $this->connection->prepare($sql);
$success = $stmt->execute($params);
if (! $success) {
throw new DatabaseException("Query failed: " . implode(', ', $stmt->errorInfo()));
}
return new DatabaseResult(
statement: $stmt,
executionTime: microtime(true) - $startTime,
affectedRows: $stmt->rowCount()
);
}
/**
* Führt mehrere Queries parallel aus
*
* @param array<string, array> $queries ['key' => ['sql' => '...', 'params' => []], ...]
* @return array<string, DatabaseResult>
*/
public function queryMultiple(array $queries): array
{
$operations = [];
foreach ($queries as $key => $query) {
$operations[$key] = fn () => $this->query($query['sql'], $query['params'] ?? []);
}
return $this->fiberManager->batch($operations);
}
/**
* Führt Queries mit begrenzter Parallelität aus
*/
public function queryBatch(array $queries, int $maxConcurrency = 5): array
{
$pool = new AsyncPool($maxConcurrency, $this->fiberManager);
foreach ($queries as $key => $query) {
$pool->add(
fn () => $this->query($query['sql'], $query['params'] ?? []),
$key
);
}
return $pool->execute();
}
/**
* Führt eine SELECT-Query aus
*/
public function select(string $sql, array $params = []): array
{
return $this->query($sql, $params)->fetchAll();
}
/**
* Führt eine INSERT-Query aus
*/
public function insert(string $table, array $data): int
{
$columns = array_keys($data);
$placeholders = array_map(fn ($col) => ":$col", $columns);
$sql = "INSERT INTO $table (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
$this->query($sql, $data);
return (int)$this->connection->lastInsertId();
}
/**
* Führt mehrere INSERTs parallel aus
*/
public function insertMultiple(string $table, array $records): array
{
$operations = [];
foreach ($records as $key => $data) {
$operations[$key] = fn () => $this->insert($table, $data);
}
return $this->fiberManager->batch($operations);
}
/**
* Führt eine UPDATE-Query aus
*/
public function update(string $table, array $data, array $where): int
{
$setParts = array_map(fn ($col) => "$col = :set_$col", array_keys($data));
$whereParts = array_map(fn ($col) => "$col = :where_$col", array_keys($where));
$sql = "UPDATE $table SET " . implode(', ', $setParts) . " WHERE " . implode(' AND ', $whereParts);
$params = [];
foreach ($data as $key => $value) {
$params["set_$key"] = $value;
}
foreach ($where as $key => $value) {
$params["where_$key"] = $value;
}
return $this->query($sql, $params)->getAffectedRows();
}
/**
* Führt eine DELETE-Query aus
*/
public function delete(string $table, array $where): int
{
$whereParts = array_map(fn ($col) => "$col = :$col", array_keys($where));
$sql = "DELETE FROM $table WHERE " . implode(' AND ', $whereParts);
return $this->query($sql, $where)->getAffectedRows();
}
/**
* Startet eine Transaktion
*/
public function beginTransaction(): void
{
$this->connection->beginTransaction();
}
/**
* Bestätigt eine Transaktion
*/
public function commit(): void
{
$this->connection->commit();
}
/**
* Bricht eine Transaktion ab
*/
public function rollback(): void
{
$this->connection->rollBack();
}
/**
* Führt eine Funktion in einer Transaktion aus
*/
public function transaction(callable $callback): mixed
{
$this->beginTransaction();
try {
$result = $callback($this);
$this->commit();
return $result;
} catch (\Throwable $e) {
$this->rollback();
throw $e;
}
}
/**
* Gibt die rohe PDO-Verbindung zurück
*/
public function getConnection(): PDO
{
return $this->connection;
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Database;
use App\Framework\Exception\FrameworkException;
/**
* Exception für Database-Fehler
*/
class DatabaseException extends FrameworkException
{
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Database;
use PDOStatement;
/**
* Database Query Result
*/
final readonly class DatabaseResult
{
public function __construct(
private PDOStatement $statement,
public float $executionTime,
public int $affectedRows
) {
}
/**
* Holt alle Zeilen
*/
public function fetchAll(): array
{
return $this->statement->fetchAll();
}
/**
* Holt eine Zeile
*/
public function fetch(): array|false
{
return $this->statement->fetch();
}
/**
* Holt einen einzelnen Wert
*/
public function fetchColumn(int $column = 0): mixed
{
return $this->statement->fetchColumn($column);
}
/**
* Gibt die Anzahl betroffener Zeilen zurück
*/
public function getAffectedRows(): int
{
return $this->affectedRows;
}
/**
* Gibt die Ausführungszeit zurück
*/
public function getExecutionTime(): float
{
return $this->executionTime;
}
}

View File

@@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Http;
use App\Framework\Async\AsyncPool;
use App\Framework\Async\FiberManager;
use Fiber;
/**
* Asynchroner HTTP-Client mit Fiber-Unterstützung
*/
final class AsyncHttpClient
{
private array $defaultOptions = [
'timeout' => 30,
'connect_timeout' => 10,
'follow_redirects' => true,
'max_redirects' => 5,
'user_agent' => 'AsyncHttpClient/1.0',
];
public function __construct(
private readonly FiberManager $fiberManager = new FiberManager(),
array $defaultOptions = []
) {
$this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions);
}
/**
* Sendet einen GET-Request
*/
public function get(string $url, array $headers = [], array $options = []): HttpResponse
{
return $this->request('GET', $url, null, $headers, $options);
}
/**
* Sendet einen POST-Request
*/
public function post(string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
{
return $this->request('POST', $url, $data, $headers, $options);
}
/**
* Sendet einen PUT-Request
*/
public function put(string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
{
return $this->request('PUT', $url, $data, $headers, $options);
}
/**
* Sendet einen DELETE-Request
*/
public function delete(string $url, array $headers = [], array $options = []): HttpResponse
{
return $this->request('DELETE', $url, null, $headers, $options);
}
/**
* Sendet mehrere Requests parallel
*
* @param array<string, array> $requests ['key' => ['method' => 'GET', 'url' => '...', ...]]
* @return array<string, HttpResponse>
*/
public function requestMultiple(array $requests): array
{
$operations = [];
foreach ($requests as $key => $request) {
$operations[$key] = fn () => $this->request(
$request['method'] ?? 'GET',
$request['url'],
$request['data'] ?? null,
$request['headers'] ?? [],
$request['options'] ?? []
);
}
return $this->fiberManager->batch($operations);
}
/**
* Sendet Requests mit begrenzter Parallelität
*/
public function requestBatch(array $requests, int $maxConcurrency = 10): array
{
$pool = new AsyncPool($maxConcurrency, $this->fiberManager);
foreach ($requests as $key => $request) {
$pool->add(
fn () => $this->request(
$request['method'] ?? 'GET',
$request['url'],
$request['data'] ?? null,
$request['headers'] ?? [],
$request['options'] ?? []
),
$key
);
}
return $pool->execute();
}
/**
* Hauptmethode für HTTP-Requests
*/
private function request(string $method, string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
{
$options = array_merge($this->defaultOptions, $options);
$context = $this->createContext($method, $data, $headers, $options);
$startTime = microtime(true);
try {
$content = @file_get_contents($url, false, $context);
if ($content === false) {
$error = error_get_last();
throw new HttpException("HTTP request failed: " . ($error['message'] ?? 'Unknown error'));
}
$responseHeaders = $this->parseHeaders($http_response_header ?? []);
$statusCode = $this->extractStatusCode($http_response_header ?? []);
return new HttpResponse(
statusCode: $statusCode,
headers: $responseHeaders,
body: $content,
requestTime: microtime(true) - $startTime
);
} catch (\Throwable $e) {
throw new HttpException("HTTP request failed: " . $e->getMessage(), 0, $e);
}
}
/**
* @return resource
*/
private function createContext(string $method, mixed $data, array $headers, array $options)
{
$contextOptions = [
'http' => [
'method' => $method,
'timeout' => $options['timeout'],
'user_agent' => $options['user_agent'],
'follow_location' => $options['follow_redirects'],
'max_redirects' => $options['max_redirects'],
'ignore_errors' => true,
],
];
if ($data !== null) {
if (is_array($data) || is_object($data)) {
$contextOptions['http']['content'] = json_encode($data);
$headers['Content-Type'] = 'application/json';
} else {
$contextOptions['http']['content'] = (string)$data;
}
}
if (! empty($headers)) {
$headerStrings = [];
foreach ($headers as $key => $value) {
$headerStrings[] = "$key: $value";
}
$contextOptions['http']['header'] = implode("\r\n", $headerStrings);
}
return stream_context_create($contextOptions);
}
private function parseHeaders(array $httpResponseHeader): array
{
$headers = [];
foreach ($httpResponseHeader as $header) {
if (strpos($header, ':') !== false) {
[$key, $value] = explode(':', $header, 2);
$headers[trim($key)] = trim($value);
}
}
return $headers;
}
private function extractStatusCode(array $httpResponseHeader): int
{
if (empty($httpResponseHeader)) {
return 0;
}
$statusLine = $httpResponseHeader[0];
if (preg_match('/HTTP\/\d+\.\d+\s+(\d+)/', $statusLine, $matches)) {
return (int)$matches[1];
}
return 0;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Http;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
/**
* Exception für HTTP-Fehler
*/
class HttpException extends FrameworkException
{
public function __construct(
string $message = '',
int $code = 0,
?\Throwable $previous = null,
public readonly ?HttpResponse $response = null
) {
$context = ExceptionContext::forOperation('http.request', 'http')
->withData([
'response_code' => $response?->statusCode ?? null,
'response_headers' => $response?->headers ?? null,
]);
parent::__construct($message, $context, $code, $previous);
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Http;
/**
* HTTP Response Objekt
*/
final readonly class HttpResponse
{
public function __construct(
public int $statusCode,
public array $headers,
public string $body,
public float $requestTime
) {
}
/**
* Prüft ob Response erfolgreich war
*/
public function isSuccess(): bool
{
return $this->statusCode >= 200 && $this->statusCode < 300;
}
/**
* Dekodiert JSON Response
*/
public function json(): array
{
return json_decode($this->body, true) ?? [];
}
/**
* Gibt spezifischen Header zurück
*/
public function getHeader(string $name): ?string
{
return $this->headers[$name] ?? null;
}
/**
* Gibt Content-Type zurück
*/
public function getContentType(): ?string
{
return $this->getHeader('Content-Type');
}
/**
* Konvertiert zu Array
*/
public function toArray(): array
{
return [
'status_code' => $this->statusCode,
'headers' => $this->headers,
'body' => $this->body,
'request_time' => $this->requestTime,
];
}
}

View File

@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Mail;
use App\Framework\Async\AsyncQueue;
use App\Framework\Async\FiberManager;
/**
* Asynchroner Mailer für Bulk-Email-Versand
*/
final class AsyncMailer
{
private array $config;
private AsyncQueue $mailQueue;
public function __construct(
array $config = [],
private readonly FiberManager $fiberManager = new FiberManager()
) {
$this->config = array_merge([
'smtp_host' => 'localhost',
'smtp_port' => 587,
'smtp_username' => '',
'smtp_password' => '',
'smtp_encryption' => 'tls',
'from_email' => 'noreply@example.com',
'from_name' => 'Framework Mailer',
'batch_size' => 10,
], $config);
$this->mailQueue = new AsyncQueue(1000);
}
/**
* Sendet eine einzelne E-Mail
*/
public function send(Email $email): bool
{
try {
return $this->sendEmail($email);
} catch (\Throwable $e) {
error_log("Failed to send email: " . $e->getMessage());
return false;
}
}
/**
* Sendet mehrere E-Mails parallel
*/
public function sendMultiple(array $emails): array
{
$operations = [];
foreach ($emails as $key => $email) {
$operations[$key] = fn () => $this->send($email);
}
return $this->fiberManager->batch($operations);
}
/**
* Fügt E-Mails zur Queue hinzu
*/
public function queue(Email $email): bool
{
return $this->mailQueue->enqueue($email);
}
/**
* Fügt mehrere E-Mails zur Queue hinzu
*/
public function queueMultiple(array $emails): int
{
$queued = 0;
foreach ($emails as $email) {
if ($this->queue($email)) {
$queued++;
}
}
return $queued;
}
/**
* Startet den Queue-Worker
*/
public function startWorker(): void
{
$this->fiberManager->async(function () {
while (true) {
$email = $this->mailQueue->dequeue();
if ($email === null) {
break; // Queue geschlossen
}
if ($email instanceof Email) {
$this->send($email);
}
}
});
}
/**
* Verarbeitet Queue in Batches
*/
public function processQueue(): int
{
$processed = 0;
$batch = [];
// Sammle Batch
for ($i = 0; $i < $this->config['batch_size']; $i++) {
$email = $this->mailQueue->tryDequeue();
if ($email === null) {
break;
}
$batch[] = $email;
}
if (! empty($batch)) {
$results = $this->sendMultiple($batch);
$processed = count(array_filter($results));
}
return $processed;
}
/**
* Sendet Newsletter an mehrere Empfänger
*/
public function sendNewsletter(string $subject, string $content, array $recipients): array
{
$emails = [];
foreach ($recipients as $key => $recipient) {
$email = new Email(
to: $recipient['email'],
subject: $subject,
body: $this->personalizeContent($content, $recipient),
fromEmail: $this->config['from_email'],
fromName: $this->config['from_name']
);
$emails[$key] = $email;
}
return $this->sendMultiple($emails);
}
private function sendEmail(Email $email): bool
{
// Vereinfachte SMTP-Implementation
// In Produktion würde man eine echte SMTP-Library verwenden
$headers = [
'From: ' . $email->fromName . ' <' . $email->fromEmail . '>',
'Reply-To: ' . $email->fromEmail,
'Content-Type: ' . ($email->isHtml ? 'text/html' : 'text/plain') . '; charset=UTF-8',
'MIME-Version: 1.0',
];
if (! empty($email->cc)) {
$headers[] = 'Cc: ' . implode(', ', $email->cc);
}
if (! empty($email->bcc)) {
$headers[] = 'Bcc: ' . implode(', ', $email->bcc);
}
$success = @mail(
$email->to,
$email->subject,
$email->body,
implode("\r\n", $headers)
);
return $success !== false;
}
private function personalizeContent(string $content, array $recipient): string
{
$placeholders = [
'{{name}}' => $recipient['name'] ?? '',
'{{email}}' => $recipient['email'] ?? '',
'{{first_name}}' => $recipient['first_name'] ?? '',
'{{last_name}}' => $recipient['last_name'] ?? '',
];
return str_replace(array_keys($placeholders), array_values($placeholders), $content);
}
/**
* Stoppt den Mail-Queue-Worker
*/
public function stopWorker(): void
{
$this->mailQueue->close();
}
/**
* Gibt Mailer-Statistiken zurück
*/
public function getStats(): array
{
return [
'queue_stats' => $this->mailQueue->getStats(),
'config' => [
'batch_size' => $this->config['batch_size'],
'smtp_host' => $this->config['smtp_host'],
],
];
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace App\Framework\AsyncExamples\Mail;
/**
* E-Mail Datenklasse
*/
final readonly class Email
{
public function __construct(
public string $to,
public string $subject,
public string $body,
public string $fromEmail,
public string $fromName = '',
public array $cc = [],
public array $bcc = [],
public bool $isHtml = false,
public array $attachments = []
) {
}
/**
* Erstellt HTML-E-Mail
*/
public static function html(string $to, string $subject, string $body, string $fromEmail, string $fromName = ''): self
{
return new self($to, $subject, $body, $fromEmail, $fromName, isHtml: true);
}
/**
* Erstellt Text-E-Mail
*/
public static function text(string $to, string $subject, string $body, string $fromEmail, string $fromName = ''): self
{
return new self($to, $subject, $body, $fromEmail, $fromName, isHtml: false);
}
/**
* Fügt CC-Empfänger hinzu
*/
public function withCc(array $cc): self
{
return new self(
$this->to,
$this->subject,
$this->body,
$this->fromEmail,
$this->fromName,
array_merge($this->cc, $cc),
$this->bcc,
$this->isHtml,
$this->attachments
);
}
/**
* Fügt BCC-Empfänger hinzu
*/
public function withBcc(array $bcc): self
{
return new self(
$this->to,
$this->subject,
$this->body,
$this->fromEmail,
$this->fromName,
$this->cc,
array_merge($this->bcc, $bcc),
$this->isHtml,
$this->attachments
);
}
}

View File

@@ -0,0 +1,58 @@
# AsyncExamples - Fiber-basierte Framework-Komponenten
Dieses Verzeichnis enthält vollständige Implementierungen verschiedener Framework-Komponenten mit Fiber-Unterstützung als Beispiele und Vorlagen für asynchrone Programmierung.
## Struktur
### `/Http` - HTTP-Client
- **AsyncHttpClient**: Vollständiger HTTP-Client mit parallelen Requests
- **HttpResponse**: Response-Objekt mit JSON-Dekodierung
- **HttpException**: Exception-Handling für HTTP-Fehler
**Features:**
- Parallele HTTP-Requests
- Batch-Processing mit konfigurierbarer Parallelität
- Automatisches Header-Parsing
- JSON-Response-Handling
### `/Database` - Database Layer
- **AsyncDatabase**: PDO-basierter Database Layer mit Fiber-Unterstützung
- **DatabaseResult**: Result-Objekt mit Performance-Metriken
- **DatabaseException**: Database-spezifische Exceptions
**Features:**
- Parallele Datenbankabfragen
- Transaction-Support
- Batch-INSERTs/UPDATEs
- Query-Performance-Tracking
### `/Cache` - Cache System
- **AsyncCache**: Multi-Level-Cache mit Memory und File-Backend
**Features:**
- Memory + File-Cache-Kombination
- Paralleles Laden/Speichern
- TTL-Support
- Cache-Statistiken
### `/Mail` - Email System
- **AsyncMailer**: Bulk-Email-Versand mit Queue-System
- **Email**: Email-Datenklasse mit Builder-Pattern
**Features:**
- Paralleler E-Mail-Versand
- Queue-basierte Verarbeitung
- Newsletter-Funktionen
- Template-Personalisierung
### `/Assets` - Asset Processing
- **AsyncAssetProcessor**: CSS/JS/Image-Verarbeitung mit Watch-Mode
**Features:**
- Parallele Asset-Verarbeitung
- CSS/SCSS-Kompilierung
- JavaScript-Minifizierung
- Bildoptimierung
- File-Watching
## Nutzung