weitere Bearibeitung

This commit is contained in:
2026-06-17 16:45:14 +02:00
parent 06645f1e9c
commit 1891ec0a51
38 changed files with 2720 additions and 109 deletions
+123
View File
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace App\Core;
use PDO;
use PDOException;
use RuntimeException;
/**
* SQLite-basierte Datenbank für Test-/Entwicklungsumgebungen.
* Ersetzt die MySQL-Database-Klasse und emuliert MySQL-spezifische Features.
*/
final class DatabaseSqlite implements DatabaseInterface
{
private ?PDO $pdo = null;
private readonly string $dbPath;
public function __construct(private readonly array $config = [])
{
$this->dbPath = $this->config['path']
?? dirname(__DIR__, 2) . '/storage/database.sqlite';
}
public function pdo(): PDO
{
if ($this->pdo instanceof PDO) {
return $this->pdo;
}
$dbDir = dirname($this->dbPath);
if (!is_dir($dbDir) && !mkdir($dbDir, 0775, true) && !is_dir($dbDir)) {
throw new RuntimeException('Das Datenbank-Verzeichnis konnte nicht erstellt werden: ' . $dbDir);
}
try {
$this->pdo = new PDO(
'sqlite:' . $this->dbPath,
null,
null,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
$this->pdo->exec('PRAGMA journal_mode=WAL');
$this->pdo->exec('PRAGMA foreign_keys=ON');
$this->pdo->exec('PRAGMA busy_timeout=5000');
} catch (PDOException $exception) {
throw new PDOException(
'SQLite connection failed: ' . $exception->getMessage(),
(int) $exception->getCode(),
$exception
);
}
return $this->pdo;
}
public function isConfigured(): bool
{
return $this->dbPath !== '';
}
/**
* Führt schema.sqlite.sql aus, um die Datenbank zu initialisieren.
*/
public function initializeSchema(): void
{
$schemaFile = dirname(__DIR__, 2) . '/database/schema.sqlite.sql';
if (!is_file($schemaFile)) {
throw new RuntimeException('SQLite-Schema nicht gefunden: ' . $schemaFile);
}
$schema = file_get_contents($schemaFile);
if (!is_string($schema) || $schema === '') {
throw new RuntimeException('SQLite-Schema konnte nicht geladen werden.');
}
$this->pdo()->exec($schema);
}
/**
* SQLite-kompatibler UPSERT: INSERT OR REPLACE + SELECT als Alternative zu ON DUPLICATE KEY.
*/
public function upsertRateLimit(string $actionKey, string $bucketKey, string $now): void
{
$pdo = $this->pdo();
$existing = $pdo->prepare(
'SELECT id FROM rate_limits WHERE action_key = :action_key AND bucket_key = :bucket_key LIMIT 1'
);
$existing->execute([
'action_key' => $actionKey,
'bucket_key' => $bucketKey,
]);
if (!$existing->fetchColumn()) {
$insert = $pdo->prepare(
'INSERT INTO rate_limits (action_key, bucket_key, attempts, window_start, last_attempt_at, created_at)
VALUES (:action_key, :bucket_key, 0, :window_start, :last_attempt_at, :created_at)'
);
$insert->execute([
'action_key' => $actionKey,
'bucket_key' => $bucketKey,
'window_start' => $now,
'last_attempt_at' => $now,
'created_at' => $now,
]);
}
}
public function getDbPath(): string
{
return $this->dbPath;
}
}