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:
312
src/Framework/AsyncExamples/Assets/AsyncAssetProcessor.php
Normal file
312
src/Framework/AsyncExamples/Assets/AsyncAssetProcessor.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
263
src/Framework/AsyncExamples/Cache/AsyncCache.php
Normal file
263
src/Framework/AsyncExamples/Cache/AsyncCache.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
206
src/Framework/AsyncExamples/Database/AsyncDatabase.php
Normal file
206
src/Framework/AsyncExamples/Database/AsyncDatabase.php
Normal 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;
|
||||
}
|
||||
}
|
||||
14
src/Framework/AsyncExamples/Database/DatabaseException.php
Normal file
14
src/Framework/AsyncExamples/Database/DatabaseException.php
Normal 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
|
||||
{
|
||||
}
|
||||
60
src/Framework/AsyncExamples/Database/DatabaseResult.php
Normal file
60
src/Framework/AsyncExamples/Database/DatabaseResult.php
Normal 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;
|
||||
}
|
||||
}
|
||||
205
src/Framework/AsyncExamples/Http/AsyncHttpClient.php
Normal file
205
src/Framework/AsyncExamples/Http/AsyncHttpClient.php
Normal 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;
|
||||
}
|
||||
}
|
||||
29
src/Framework/AsyncExamples/Http/HttpException.php
Normal file
29
src/Framework/AsyncExamples/Http/HttpException.php
Normal 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);
|
||||
}
|
||||
}
|
||||
64
src/Framework/AsyncExamples/Http/HttpResponse.php
Normal file
64
src/Framework/AsyncExamples/Http/HttpResponse.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
215
src/Framework/AsyncExamples/Mail/AsyncMailer.php
Normal file
215
src/Framework/AsyncExamples/Mail/AsyncMailer.php
Normal 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'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
76
src/Framework/AsyncExamples/Mail/Email.php
Normal file
76
src/Framework/AsyncExamples/Mail/Email.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
58
src/Framework/AsyncExamples/README.md
Normal file
58
src/Framework/AsyncExamples/README.md
Normal 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
|
||||
Reference in New Issue
Block a user