首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 文本數據庫 (Flat File DB) 完整 CRUD 實戰:留言本系統從零構建

     編輯:PHP 相關知識

適用場景

  • 極簡部署 (無 MySQL/Redis)、微型項目、嵌入式設備、教學演示
  • 數據量 < 10 萬條、並發低、結構固定、容忍最終一致性

數據設計

文件:gb.dat,每行一條記錄,字段間用 | 分隔:

IP|Datetime|Name|Email|Homepage|Face|QQ|Avatar|Content|Reply
192.168.1.1|2024-01-15 10:30:00|Alice|alice@ex.com|https://a.com|smile.gif|12345|avatar.png|Hello!|
10.0.0.5|2024-01-15 10:31:22|Bob|bob@ex.com||cry.gif|||Hi there|Admin reply

核心操作封裝類

file = $filePath;
        if (!file_exists($this->file)) {
            touch($this->file);
            chmod($this->file, 0644);
        }
    }

    // 讀取所有記錄 (返回關聯數組)
    private function readAll(): array {
        $lines = file($this->file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        $records = [];
        foreach ($lines as $line) {
            $parts = explode($this->delimiter, $line, count($this->fields));
            $records[] = array_combine($this->fields, $parts);
        }
        return array_reverse($records); // 最新在前
    }

    // 寫入所有記錄 (原子性: 寫臨時文件 + rename)
    private function writeAll(array $records): bool {
        $tmpFile = $this->file . '.tmp';
        $fp = fopen($tmpFile, 'w');
        if (!$fp) return false;
        foreach (array_reverse($records) as $rec) { // 存儲時舊在前
            fwrite($fp, implode($this->delimiter, $rec) . "\n");
        }
        fclose($fp);
        return rename($tmpFile, $this->file); // 原子替換
    }

    // CREATE
    public function insert(array $data): bool {
        $records = $this->readAll();
        $record = array_merge(array_fill_keys($this->fields, ''), $data);
        $record['datetime'] = date('Y-m-d H:i:s');
        $record['ip'] = $_SERVER['REMOTE_ADDR'] ?? 'cli';
        $records[] = $record;
        return $this->writeAll($records);
    }

    // READ (分頁)
    public function select(int $page = 1, int $pageSize = 20): array {
        $records = $this->readAll();
        $total = count($records);
        $offset = ($page - 1) * $pageSize;
        return [
            'data' => array_slice($records, $offset, $pageSize),
            'total' => $total,
            'page' => $page,
            'pageSize' => $pageSize,
            'totalPages' => (int)ceil($total / $pageSize)
        ];
    }

    // UPDATE (按 datetime 唯一標識)
    public function update(string $datetime, array $data): bool {
        $records = $this->readAll();
        foreach ($records as &$rec) {
            if ($rec['datetime'] === $datetime) {
                $rec = array_merge($rec, $data);
                return $this->writeAll($records);
            }
        }
        return false;
    }

    // DELETE
    public function delete(string $datetime): bool {
        $records = $this->readAll();
        $filtered = array_filter($records, fn($r) => $r['datetime'] !== $datetime);
        if (count($filtered) === count($records)) return false;
        return $this->writeAll(array_values($filtered));
    }

    // SEARCH (關鍵字模糊匹配)
    public function search(string $keyword): array {
        $records = $this->readAll();
        $keyword = strtolower(trim($keyword));
        return array_filter($records, function($r) use ($keyword) {
            return strpos(strtolower(implode(' ', $r)), $keyword) !== false;
        });
    }
}

前端控制器示例 (index.php)

insert([
            'name' => $_POST['name'],
            'email' => $_POST['email'],
            'home' => $_POST['home'],
            'face' => $_POST['face'],
            'qq' => $_POST['qq'],
            'avatar' => $_POST['avatar'],
            'content' => $_POST['content'],
        ]);
        header('Location: index.php'); exit;
    } elseif ($action === 'reply') {
        $db->update($_POST['datetime'], ['reply' => $_POST['reply']]);
        header('Location: index.php'); exit;
    } elseif ($action === 'delete') {
        $db->delete($_POST['datetime']);
        header('Location: index.php'); exit;
    }
}

// 讀取數據
$page = max(1, (int)($_GET['page'] ?? 1));
$result = $db->select($page, 10);
$searchResults = [];
if (!empty($_GET['q'])) {
    $searchResults = $db->search($_GET['q']);
}
?>

留言本

留言本

姓名:
郵箱:
主頁:
表情:
QQ:
頭像:
內容:

搜索結果: 條

留言列表 (第 / 頁,共 條)

| IP: | 郵件 | 主頁 | QQ:

管理員回復:
1): ?>
1): ?> 首頁 | 上一頁 第 頁 下一頁 | 尾頁

關鍵技術點總結

點實現作用
原子寫入tmp + rename防止寫入中斷損壞數據文件
併發安全文件鎖 flock (可加)多進程寫入不丟數據
XSS 防護htmlspecialchars 輸出轉義防止腳本注入
CSRF 防護可加 token 隱藏域防跨站請求偽造
性能優化大數據遷移 SQLite超過 10 萬行建議遷移

遷移建議

當出現以下信號,請遷移至 SQLite / MySQL:

  • 文件 > 10MB 或 行數 > 100,000
  • QPS > 50 或併發寫入 > 10
  • 需要複雜查詢 (JOIN、聚合、全文搜索)
  • 需要事務 (轉賬、扣庫存等)

文本數據庫是理解「持久化本質」的最佳教具。掌握其 CRUD 邏輯,遷移到任何數據庫都將得心應手。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved