Copy <?php
class DbStreamWrapper {
private static ?PDO $pdo = null;
private string $key = '';
private string $buffer = '';
private int $pos = 0;
public mixed $context = null;
private static function db(): PDO {
if (!self::$pdo) {
self::$pdo = new PDO('sqlite:/tmp/stream_store.db');
self::$pdo->exec('CREATE TABLE IF NOT EXISTS kv (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER DEFAULT (strftime(\'%s\',\'now\'))
)');
}
return self::$pdo;
}
public function stream_open(string $path, string $mode, int $options, ?string &$opened): bool {
// db://bucket/key → key = "bucket/key"
$this->key = ltrim(parse_url($path, PHP_URL_HOST) . parse_url($path, PHP_URL_PATH), '/');
$this->pos = 0;
if (str_contains($mode, 'w')) {
$this->buffer = '';
self::db()->prepare('INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)')->execute([$this->key, '']);
} else {
$stmt = self::db()->prepare('SELECT value FROM kv WHERE key = ?');
$stmt->execute([$this->key]);
$this->buffer = $stmt->fetchColumn() ?: '';
}
return true;
}
public function stream_read(int $count): string {
$chunk = substr($this->buffer, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_write(string $data): int {
$this->buffer .= $data;
self::db()->prepare('INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)')->execute([$this->key, $this->buffer]);
return strlen($data);
}
public function stream_eof(): bool { return $this->pos >= strlen($this->buffer); }
public function stream_tell(): int { return $this->pos; }
public function stream_flush(): bool { return true; }
public function stream_close(): void {}
public function stream_seek(int $offset, int $whence = SEEK_SET): bool {
$size = strlen($this->buffer);
$this->pos = match ($whence) {
SEEK_SET => $offset,
SEEK_CUR => $this->pos + $offset,
SEEK_END => $size + $offset,
default => $this->pos,
};
return true;
}
public function stream_stat(): array {
return ['size' => strlen($this->buffer), 'mtime' => time(), 'mode' => 0100644];
}
public function url_stat(string $path, int $flags): array|false {
$key = ltrim(parse_url($path, PHP_URL_HOST) . parse_url($path, PHP_URL_PATH), '/');
$stmt = self::db()->prepare('SELECT length(value) FROM kv WHERE key = ?');
$stmt->execute([$key]);
$size = $stmt->fetchColumn();
if ($size === false) return false;
return ['size' => (int)$size, 'mtime' => time(), 'mode' => 0100644];
}
public function unlink(string $path): bool {
$key = ltrim(parse_url($path, PHP_URL_HOST) . parse_url($path, PHP_URL_PATH), '/');
self::db()->prepare('DELETE FROM kv WHERE key = ?')->execute([$key]);
return true;
}
}
stream_wrapper_register('db', DbStreamWrapper::class);
// Store config values
file_put_contents('db://config/app.json', json_encode([
'debug' => false,
'db_host' => 'localhost',
'workers' => 4,
]));
file_put_contents('db://cache/user:1001', serialize([
'id' => 1001, 'name' => 'Alice', 'role' => 'admin'
]));
// Read them back
$config = json_decode(file_get_contents('db://config/app.json'), true);
echo "Config: db_host={$config['db_host']}, workers={$config['workers']}\n";
$user = unserialize(file_get_contents('db://cache/user:1001'));
echo "User: id={$user['id']}, name={$user['name']}, role={$user['role']}\n";
// file_exists via url_stat
echo "config/app.json exists: " . (file_exists('db://config/app.json') ? 'yes' : 'no') . "\n";
echo "config/missing exists: " . (file_exists('db://config/missing') ? 'yes' : 'no') . "\n";
// Append log entries
$fh = fopen('db://logs/audit', 'a');
fwrite($fh, date('Y-m-d H:i:s') . " user:1001 login\n");
fwrite($fh, date('Y-m-d H:i:s') . " user:1001 viewed /dashboard\n");
fclose($fh);
echo "\nAudit log:\n" . file_get_contents('db://logs/audit');