weitere Bearibeitung
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user