fix: resolve RedisCache array offset error and improve discovery diagnostics

- Fix RedisCache driver to handle MGET failures gracefully with fallback
- Add comprehensive discovery context comparison debug tools
- Identify root cause: WEB context discovery missing 166 items vs CLI
- WEB context missing RequestFactory class entirely (52 vs 69 commands)
- Improved exception handling with detailed binding diagnostics
This commit is contained in:
2025-09-12 20:05:18 +02:00
parent 8040d3e7a5
commit e30753ba0e
46990 changed files with 10789682 additions and 89639 deletions

View File

@@ -15,15 +15,19 @@ final class AsyncAssetProcessor
public function __construct(
private readonly Storage $storage,
private readonly FiberManager $fiberManager = new FiberManager(),
/** @var array<string, mixed> */
private readonly array $config = []
) {
}
/**
* Verarbeitet alle Assets parallel
*
* @return array<string, mixed>
*/
public function processAll(string $sourceDir, string $outputDir): array
{
/** @var array<string, \Closure> */
$operations = [
'css' => fn () => $this->processCss($sourceDir . '/css', $outputDir . '/css'),
'js' => fn () => $this->processJs($sourceDir . '/js', $outputDir . '/js'),
@@ -35,12 +39,15 @@ final class AsyncAssetProcessor
/**
* Verarbeitet CSS-Dateien
*
* @return array<string, string>
*/
public function processCss(string $sourceDir, string $outputDir): array
{
$cssFiles = $this->findFiles($sourceDir, '*.css');
$scssFiles = $this->findFiles($sourceDir, '*.scss');
/** @var array<string, \Closure> */
$operations = [];
// Verarbeite CSS-Dateien
@@ -58,11 +65,14 @@ final class AsyncAssetProcessor
/**
* Verarbeitet JavaScript-Dateien
*
* @return array<string, string>
*/
public function processJs(string $sourceDir, string $outputDir): array
{
$jsFiles = $this->findFiles($sourceDir, '*.js');
/** @var array<string, \Closure> */
$operations = [];
foreach ($jsFiles as $file) {
$operations["js_{$file}"] = fn () => $this->minifyJs($sourceDir . '/' . $file, $outputDir);
@@ -73,6 +83,8 @@ final class AsyncAssetProcessor
/**
* Verarbeitet Bilder
*
* @return array<string, string>
*/
public function processImages(string $sourceDir, string $outputDir): array
{
@@ -84,6 +96,7 @@ final class AsyncAssetProcessor
$this->findFiles($sourceDir, '*.svg')
);
/** @var array<string, \Closure> */
$operations = [];
foreach ($imageFiles as $file) {
$operations["img_{$file}"] = fn () => $this->optimizeImage($sourceDir . '/' . $file, $outputDir);
@@ -94,9 +107,12 @@ final class AsyncAssetProcessor
/**
* Bündelt JavaScript-Dateien
*
* @param array<int, string> $files
*/
public function bundleJs(array $files, string $outputFile): string
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($files as $file) {
$operations[$file] = fn () => $this->storage->get($file);
@@ -113,9 +129,12 @@ final class AsyncAssetProcessor
/**
* Bündelt CSS-Dateien
*
* @param array<int, string> $files
*/
public function bundleCss(array $files, string $outputFile): string
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($files as $file) {
$operations[$file] = fn () => $this->storage->get($file);
@@ -132,9 +151,13 @@ final class AsyncAssetProcessor
/**
* Generiert verschiedene Bildgrößen parallel
*
* @param array<string, array<string, mixed>> $sizes
* @return array<string, string>
*/
public function generateImageSizes(string $sourceImage, array $sizes, string $outputDir): array
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($sizes as $sizeName => $dimensions) {
$operations[$sizeName] = fn () => $this->resizeImage(
@@ -235,6 +258,7 @@ final class AsyncAssetProcessor
// Vereinfachte Variable-Verarbeitung
preg_match_all('/\$([a-zA-Z0-9_-]+)\s*:\s*([^;]+);/', $content, $matches);
/** @var array<string, string> */
$variables = [];
for ($i = 0; $i < count($matches[0]); $i++) {
$variables['$' . $matches[1][$i]] = $matches[2][$i];
@@ -250,6 +274,9 @@ final class AsyncAssetProcessor
return $content;
}
/**
* @return array<int, string>
*/
private function findFiles(string $directory, string $pattern): array
{
if (! is_dir($directory)) {
@@ -267,9 +294,11 @@ final class AsyncAssetProcessor
public function watch(string $sourceDir, string $outputDir): void
{
$this->fiberManager->async(function () use ($sourceDir, $outputDir) {
/** @var array<string, int|false> */
$lastCheck = [];
while (true) {
/** @var array<int, string> */
$files = array_merge(
glob($sourceDir . '/**/*.css') ?: [],
glob($sourceDir . '/**/*.scss') ?: [],

View File

@@ -11,6 +11,7 @@ use App\Framework\Async\FiberManager;
*/
final class AsyncCache
{
/** @var array<string, array<string, mixed>> */
private array $memoryCache = [];
private string $cacheDir;
@@ -68,6 +69,7 @@ final class AsyncCache
$ttl ??= $this->defaultTtl;
$expires = $ttl > 0 ? time() + $ttl : 0;
/** @var array<string, mixed> */
$item = [
'value' => $value,
'expires' => $expires,
@@ -89,9 +91,13 @@ final class AsyncCache
/**
* Holt mehrere Werte parallel
*
* @param array<int, string> $keys
* @return array<string, mixed>
*/
public function getMultiple(array $keys): array
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($keys as $key) {
$operations[$key] = fn () => $this->get($key);
@@ -102,9 +108,12 @@ final class AsyncCache
/**
* Speichert mehrere Werte parallel
*
* @param array<string, mixed> $items
*/
public function setMultiple(array $items, ?int $ttl = null): bool
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($items as $key => $value) {
$operations[$key] = fn () => $this->set($key, $value, $ttl);
@@ -139,9 +148,12 @@ final class AsyncCache
/**
* Löscht mehrere Cache-Einträge parallel
*
* @param array<int, string> $keys
*/
public function deleteMultiple(array $keys): bool
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($keys as $key) {
$operations[$key] = fn () => $this->delete($key);
@@ -175,6 +187,7 @@ final class AsyncCache
$files = glob($this->cacheDir . '/*.cache');
if ($files) {
/** @var array<int, \Closure> */
$operations = [];
foreach ($files as $file) {
$operations[] = fn () => @unlink($file);
@@ -202,10 +215,15 @@ final class AsyncCache
/**
* Erstellt mehrere Cache-Werte parallel falls sie nicht existieren
*
* @param array<string, callable> $callbacks
* @return array<string, mixed>
*/
public function rememberMultiple(array $callbacks, ?int $ttl = null): array
{
/** @var array<string, mixed> */
$results = [];
/** @var array<string, callable> */
$toCreate = [];
// Prüfe welche Keys bereits existieren
@@ -220,6 +238,7 @@ final class AsyncCache
// Erstelle fehlende Werte parallel
if (! empty($toCreate)) {
/** @var array<string, callable> */
$operations = [];
foreach ($toCreate as $key => $callback) {
$operations[$key] = $callback;
@@ -228,6 +247,7 @@ final class AsyncCache
$newValues = $this->fiberManager->batch($operations);
// Speichere neue Werte im Cache
/** @var array<string, \Closure> */
$setOperations = [];
foreach ($newValues as $key => $value) {
$setOperations[$key] = fn () => $this->set($key, $value, $ttl);
@@ -249,6 +269,8 @@ final class AsyncCache
/**
* Gibt Cache-Statistiken zurück
*
* @return array<string, mixed>
*/
public function getStats(): array
{

View File

@@ -19,9 +19,11 @@ final class AsyncDatabase
string $dsn,
string $username = '',
string $password = '',
/** @var array<int, mixed> */
array $options = [],
private readonly FiberManager $fiberManager = new FiberManager()
) {
/** @var array<int, mixed> */
$defaultOptions = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
@@ -33,6 +35,8 @@ final class AsyncDatabase
/**
* Führt eine Query aus
*
* @param array<string, mixed> $params
*/
public function query(string $sql, array $params = []): DatabaseResult
{
@@ -55,11 +59,12 @@ final class AsyncDatabase
/**
* Führt mehrere Queries parallel aus
*
* @param array<string, array> $queries ['key' => ['sql' => '...', 'params' => []], ...]
* @param array<string, array<string, mixed>> $queries ['key' => ['sql' => '...', 'params' => []], ...]
* @return array<string, DatabaseResult>
*/
public function queryMultiple(array $queries): array
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($queries as $key => $query) {
$operations[$key] = fn () => $this->query($query['sql'], $query['params'] ?? []);
@@ -70,6 +75,9 @@ final class AsyncDatabase
/**
* Führt Queries mit begrenzter Parallelität aus
*
* @param array<string, array<string, mixed>> $queries
* @return array<string, DatabaseResult>
*/
public function queryBatch(array $queries, int $maxConcurrency = 5): array
{
@@ -87,6 +95,9 @@ final class AsyncDatabase
/**
* Führt eine SELECT-Query aus
*
* @param array<string, mixed> $params
* @return array<int, array<string, mixed>>
*/
public function select(string $sql, array $params = []): array
{
@@ -95,10 +106,14 @@ final class AsyncDatabase
/**
* Führt eine INSERT-Query aus
*
* @param array<string, mixed> $data
*/
public function insert(string $table, array $data): int
{
/** @var array<int, string> */
$columns = array_keys($data);
/** @var array<int, string> */
$placeholders = array_map(fn ($col) => ":$col", $columns);
$sql = "INSERT INTO $table (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
@@ -110,9 +125,13 @@ final class AsyncDatabase
/**
* Führt mehrere INSERTs parallel aus
*
* @param array<int, array<string, mixed>> $records
* @return array<string, int>
*/
public function insertMultiple(string $table, array $records): array
{
/** @var array<int|string, \Closure> */
$operations = [];
foreach ($records as $key => $data) {
$operations[$key] = fn () => $this->insert($table, $data);
@@ -123,14 +142,20 @@ final class AsyncDatabase
/**
* Führt eine UPDATE-Query aus
*
* @param array<string, mixed> $data
* @param array<string, mixed> $where
*/
public function update(string $table, array $data, array $where): int
{
/** @var array<int, string> */
$setParts = array_map(fn ($col) => "$col = :set_$col", array_keys($data));
/** @var array<int, string> */
$whereParts = array_map(fn ($col) => "$col = :where_$col", array_keys($where));
$sql = "UPDATE $table SET " . implode(', ', $setParts) . " WHERE " . implode(' AND ', $whereParts);
/** @var array<string, mixed> */
$params = [];
foreach ($data as $key => $value) {
$params["set_$key"] = $value;
@@ -144,9 +169,12 @@ final class AsyncDatabase
/**
* Führt eine DELETE-Query aus
*
* @param array<string, mixed> $where
*/
public function delete(string $table, array $where): int
{
/** @var array<int, string> */
$whereParts = array_map(fn ($col) => "$col = :$col", array_keys($where));
$sql = "DELETE FROM $table WHERE " . implode(' AND ', $whereParts);

View File

@@ -13,6 +13,7 @@ use Fiber;
*/
final class AsyncHttpClient
{
/** @var array<string, mixed> */
private array $defaultOptions = [
'timeout' => 30,
'connect_timeout' => 10,
@@ -23,6 +24,7 @@ final class AsyncHttpClient
public function __construct(
private readonly FiberManager $fiberManager = new FiberManager(),
/** @var array<string, mixed> */
array $defaultOptions = []
) {
$this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions);
@@ -31,7 +33,7 @@ final class AsyncHttpClient
/**
* Sendet einen GET-Request
*/
public function get(string $url, array $headers = [], array $options = []): HttpResponse
public function get(string $url, /** @var array<string, string> */ array $headers = [], /** @var array<string, mixed> */ array $options = []): HttpResponse
{
return $this->request('GET', $url, null, $headers, $options);
}
@@ -39,7 +41,7 @@ final class AsyncHttpClient
/**
* Sendet einen POST-Request
*/
public function post(string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
public function post(string $url, mixed $data = null, /** @var array<string, string> */ array $headers = [], /** @var array<string, mixed> */ array $options = []): HttpResponse
{
return $this->request('POST', $url, $data, $headers, $options);
}
@@ -47,7 +49,7 @@ final class AsyncHttpClient
/**
* Sendet einen PUT-Request
*/
public function put(string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
public function put(string $url, mixed $data = null, /** @var array<string, string> */ array $headers = [], /** @var array<string, mixed> */ array $options = []): HttpResponse
{
return $this->request('PUT', $url, $data, $headers, $options);
}
@@ -55,7 +57,7 @@ final class AsyncHttpClient
/**
* Sendet einen DELETE-Request
*/
public function delete(string $url, array $headers = [], array $options = []): HttpResponse
public function delete(string $url, /** @var array<string, string> */ array $headers = [], /** @var array<string, mixed> */ array $options = []): HttpResponse
{
return $this->request('DELETE', $url, null, $headers, $options);
}
@@ -63,11 +65,12 @@ final class AsyncHttpClient
/**
* Sendet mehrere Requests parallel
*
* @param array<string, array> $requests ['key' => ['method' => 'GET', 'url' => '...', ...]]
* @param array<string, array<string, mixed>> $requests ['key' => ['method' => 'GET', 'url' => '...', ...]]
* @return array<string, HttpResponse>
*/
public function requestMultiple(array $requests): array
{
/** @var array<string, \Closure> */
$operations = [];
foreach ($requests as $key => $request) {
$operations[$key] = fn () => $this->request(
@@ -84,6 +87,9 @@ final class AsyncHttpClient
/**
* Sendet Requests mit begrenzter Parallelität
*
* @param array<string, array<string, mixed>> $requests
* @return array<string, HttpResponse>
*/
public function requestBatch(array $requests, int $maxConcurrency = 10): array
{
@@ -108,7 +114,7 @@ final class AsyncHttpClient
/**
* Hauptmethode für HTTP-Requests
*/
private function request(string $method, string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
private function request(string $method, string $url, mixed $data = null, /** @var array<string, string> */ array $headers = [], /** @var array<string, mixed> */ array $options = []): HttpResponse
{
$options = array_merge($this->defaultOptions, $options);
@@ -143,8 +149,9 @@ final class AsyncHttpClient
/**
* @return resource
*/
private function createContext(string $method, mixed $data, array $headers, array $options)
private function createContext(string $method, mixed $data, /** @var array<string, string> */ array $headers, /** @var array<string, mixed> */ array $options)
{
/** @var array<string, array<string, mixed>> */
$contextOptions = [
'http' => [
'method' => $method,
@@ -166,6 +173,7 @@ final class AsyncHttpClient
}
if (! empty($headers)) {
/** @var array<int, string> */
$headerStrings = [];
foreach ($headers as $key => $value) {
$headerStrings[] = "$key: $value";
@@ -176,8 +184,13 @@ final class AsyncHttpClient
return stream_context_create($contextOptions);
}
/**
* @param array<int, string> $httpResponseHeader
* @return array<string, string>
*/
private function parseHeaders(array $httpResponseHeader): array
{
/** @var array<string, string> */
$headers = [];
foreach ($httpResponseHeader as $header) {
if (strpos($header, ':') !== false) {
@@ -189,6 +202,9 @@ final class AsyncHttpClient
return $headers;
}
/**
* @param array<int, string> $httpResponseHeader
*/
private function extractStatusCode(array $httpResponseHeader): int
{
if (empty($httpResponseHeader)) {

View File

@@ -53,6 +53,8 @@ final readonly class HttpResponse
/**
* Konvertiert zu Array
*
* @return array<string, mixed>
*/
public function toArray(): array
{

View File

@@ -12,11 +12,13 @@ use App\Framework\Async\FiberManager;
*/
final class AsyncMailer
{
/** @var array<string, mixed> */
private array $config;
private AsyncQueue $mailQueue;
public function __construct(
/** @var array<string, mixed> */
array $config = [],
private readonly FiberManager $fiberManager = new FiberManager()
) {
@@ -50,9 +52,13 @@ final class AsyncMailer
/**
* Sendet mehrere E-Mails parallel
*
* @param array<int, Email> $emails
* @return array<int, bool>
*/
public function sendMultiple(array $emails): array
{
/** @var array<int, \Closure> */
$operations = [];
foreach ($emails as $key => $email) {
$operations[$key] = fn () => $this->send($email);
@@ -71,6 +77,8 @@ final class AsyncMailer
/**
* Fügt mehrere E-Mails zur Queue hinzu
*
* @param array<int, Email> $emails
*/
public function queueMultiple(array $emails): int
{
@@ -110,6 +118,7 @@ final class AsyncMailer
public function processQueue(): int
{
$processed = 0;
/** @var array<int, Email> */
$batch = [];
// Sammle Batch
@@ -131,9 +140,13 @@ final class AsyncMailer
/**
* Sendet Newsletter an mehrere Empfänger
*
* @param array<int, array<string, mixed>> $recipients
* @return array<int, bool>
*/
public function sendNewsletter(string $subject, string $content, array $recipients): array
{
/** @var array<int, Email> */
$emails = [];
foreach ($recipients as $key => $recipient) {
$email = new Email(
@@ -154,6 +167,7 @@ final class AsyncMailer
// Vereinfachte SMTP-Implementation
// In Produktion würde man eine echte SMTP-Library verwenden
/** @var array<int, string> */
$headers = [
'From: ' . $email->fromName . ' <' . $email->fromEmail . '>',
'Reply-To: ' . $email->fromEmail,
@@ -179,8 +193,12 @@ final class AsyncMailer
return $success !== false;
}
/**
* @param array<string, mixed> $recipient
*/
private function personalizeContent(string $content, array $recipient): string
{
/** @var array<string, string> */
$placeholders = [
'{{name}}' => $recipient['name'] ?? '',
'{{email}}' => $recipient['email'] ?? '',
@@ -201,6 +219,8 @@ final class AsyncMailer
/**
* Gibt Mailer-Statistiken zurück
*
* @return array<string, mixed>
*/
public function getStats(): array
{