PHPでJSONファイルを安全に管理する方法|flock()による排他制御と出力管理をわかりやすく解説
PHPで小規模なWebシステムを作るとき、「データベースを使わずに簡単にデータを保存したい」と思ったことはありませんか?
そんなときによく利用されるのがJSONファイルをデータベース代わりに利用する方法です。
今回紹介するコードでは、ユーザー情報やメッセージをJSONファイルへ保存しています。ただ保存するだけではなく、複数ユーザーが同時にアクセスしてもデータが壊れないようにflock()による排他制御も実装されています。
結論からいうと、このシステムは「JSONファイルを簡易データベースとして利用し、全件読み込み→メモリ上で更新→全件上書き保存する仕組み」です。
PHPの出力(永続化データ)はJSONファイルで管理されている
このシステムではデータベースを使わず、次の2つのJSONファイルが実質的なデータベースになっています。
project
├─ data
│ ├─ users.json
│ └─ messages.json
└─ lib
└─ common.php
保存先は定数で管理されています。
// データ保存先ディレクトリ
define('DATA_DIR', __DIR__ . '/../data');
// ユーザー情報
define('USERS_FILE', DATA_DIR . '/users.json');
// メッセージ情報
define('MESSAGES_FILE', DATA_DIR . '/messages.json');
例えば次のコードを実行すると、users.jsonへ保存されます。
// ユーザー一覧を保存
saveUsers($users);
PHPでJSONファイルを読み込む仕組み
すべての読み込み処理はreadJsonFile()を経由します。
function readJsonFile(string $path): array
読み込みの流れ
JSONファイル
↓
fopen()
↓
LOCK_SH取得
↓
内容を読み込む
↓
json_decode()
↓
PHP配列として返す
途中で使われている重要な処理がこちらです。
// 読み取り専用の共有ロック
flock($fp, LOCK_SH);
LOCK_SHは「共有ロック」と呼ばれます。
| 処理 | 実行可否 |
|---|---|
| 読込A | ○ |
| 読込B | ○ |
| 書込 | ×(待機) |
つまり複数人が同時に閲覧しても問題ありません。しかし、書き込み中のデータを途中で読んでしまうことは防止されています。
PHPでJSONファイルへ書き込む仕組み
保存処理はすべてwriteJsonFile()が担当します。
function writeJsonFile(string $path, array $data): bool
保存の流れ
PHP配列
↓
json_encode()
↓
LOCK_EX取得
↓
既存データ削除
↓
新しいJSONを書込
↓
保存完了
もっとも重要なのが排他ロックです。
// 書き込み中は他の処理を待機させる
flock($fp, LOCK_EX);
| 処理 | 実行可否 |
|---|---|
| 読込 | × |
| 書込 | × |
| 別の書込 | × |
例えば2人が同時に投稿した場合でも次のように順番待ちになります。
ユーザーA投稿
↓
LOCK_EX取得
↓
保存
↓
LOCK解除
↓
ユーザーB保存開始
この仕組みによってファイル破損を防いでいます。
全件上書き保存方式とは?
このシステムは「追記保存」ではありません。
毎回すべて読み込み、更新してから保存しています。
全件読込
↓
配列更新
↓
JSON再生成
↓
全件上書き保存
保存時には次の2つが実行されます。
// 既存データを空にする
ftruncate($fp, 0);
// 書き込み位置を先頭へ戻す
rewind($fp);
その後、新しいJSONを書き込みます。
// 更新後のJSONを書き込む
fwrite($fp, $json);
そのため、常に最新状態のJSONファイルが完成します。
ユーザー情報はどのように保存されるのか
新規登録時はcreateUser()が実行されます。
$newUser = [
'id' => uniqid('user_', true),
'username' => $username,
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
'created_at' => date('Y-m-d H:i:s'),
];
保存後のusers.jsonは次のようになります。
[
{
"id":"user_xxx",
"username":"suzuki",
"password_hash":"$2y$10$...",
"created_at":"2026-07-13 15:00:00"
}
]
メッセージはどのように保存されるのか
投稿時はaddMessage()が呼ばれます。
$newMessage = [
'id' => uniqid('msg_', true),
'user_id' => $userId,
'username' => $username,
'message' => $message,
'created_at' => date('Y-m-d H:i:s'),
];
messages.jsonには次のように保存されます。
[
{
"id":"msg_1",
"user_id":"user_1",
"username":"suzuki",
"message":"こんにちは",
"created_at":"2026-07-13 15:10:00"
}
]
500件制限がある理由
このコードには実務でもよく使われる工夫があります。
// 最新500件だけ保持する
if (count($messages) > 500) {
$messages = array_slice($messages, -500);
}
古いデータを削除することで次のメリットがあります。
- JSONファイルの肥大化を防ぐ
- メモリ使用量を抑える
- 読み込み速度を維持する
- 保存時間を短縮できる
実際の保存タイミング
ユーザー登録
createUser()
↓
saveUsers()
↓
writeJsonFile()
↓
users.json更新
メッセージ投稿
addMessage()
↓
saveMessages()
↓
writeJsonFile()
↓
messages.json更新
よくある失敗例
- flock()を使わず同時書き込みでファイルが壊れる
- json_decode()の失敗チェックをしていない
- password_hash()を使わず平文パスワードを保存する
- 大量データでもJSON保存を続けてしまう
- 例外処理を実装せず保存失敗に気付けない
実務でのベストプラクティス
- 必ずLOCK_SH・LOCK_EXで排他制御を行う
- JSON保存は数百件程度までにする
- パスワードはpassword_hash()で暗号化する
- 保存失敗時のログ出力を追加する
- アクセス数が増えたらSQLiteやMySQLへ移行する
この方式のメリット・デメリット
| メリット | デメリット |
|---|---|
| DB不要 | 大量データに弱い |
| 実装が簡単 | 毎回全件保存する |
| バックアップが容易 | 同時アクセスが増えると遅くなる |
| 学習しやすい | 検索性能が低い |
まとめ
このコードの最大の特徴は、JSONファイルを簡易データベースとして利用しながら、flock()による排他制御で安全性を確保している点です。
データ保存の流れは非常にシンプルです。
全件読み込み → 配列更新 → JSONへ変換 → 全件上書き保存
小規模な掲示板やチャットアプリ、学習用システムでは非常に扱いやすい構成ですが、アクセス数やデータ量が増えると処理負荷が高くなるため、本番環境ではSQLiteやMySQL、PostgreSQLなどのデータベースへの移行を検討するのが一般的です。
初心者向け学習ポイント
- JSONファイルとデータベースの違いを理解する
- json_encode()・json_decode()の使い方を覚える
- flock()による排他制御の重要性を学ぶ
- password_hash()とpassword_verify()を使った認証を理解する
- ファイル操作(fopen・fwrite・fclose)の基本を身につける
実務での活用例
- 社内ツールの試作品
- 小規模チャットシステム
- お問い合わせフォーム
- 設定ファイル管理
- 簡易CMSやメモアプリ
次に学ぶべき関連記事候補
- PHPのflock()を使った排他制御を徹底解説
- PHPでJSONファイルをデータベース代わりに使う方法
- PHPのpassword_hash()とpassword_verify()による安全な認証
- PHPでSQLiteを使ったデータ管理入門
- MySQLとPostgreSQLの違いと選び方







0 件のコメント:
コメントを投稿