解説コードを読んでみよう
このサンプルでわかること
- API とは何か(プログラムからプログラムを呼び出す窓口)
- PHP の配列を JSON に変換して返す方法(
json_encode) - JavaScript の
fetch()で API を呼び出し、JSON を受け取る方法 - データベースを使わず、ファイルにデータを保存する方法
使っているファイル
- bbs-api.php掲示板の API 本体(一覧・書き込み・削除)
- bbs.log書き込みを保存するファイル(自動で作られます。.htaccess で直接は見られないようにしています)
- index.phpAPI をブラウザから試すためのデモページ
API の使い方
URL の最後に ?api=〇〇 をつけて、何をしたいかを伝えます。結果は必ず {"result": "ok か error", "data": 中身} という形の JSON で返ります。形が決まっていると、アプリ側の処理が書きやすくなります。
| やりたいこと | URL の例 |
|---|---|
| 一覧を取得 | bbs-api.php?api=list |
| 書き込む | bbs-api.php?api=add&name=太郎&title=こんにちは&body=はじめまして |
| 削除する | bbs-api.php?api=delete&id=0&password=1234 |
1. どの処理をするか振り分ける
switch 文で api の値を見て、それぞれの関数を呼び分けます。どれにも当てはまらないときは、エラーの JSON を返します。
<?php
/*
* 掲示板の API(スマホアプリから呼び出す)
* bbs-api.php?api=list … 一覧を取得
* bbs-api.php?api=add&name=..&title=..&body=.. … 書き込み
* bbs-api.php?api=delete&id=..&password=.. … 削除
* 結果はすべて JSON で返します。データはデータベースを使わず、ファイル(bbs.log)に保存します。
*/
require_once __DIR__ . '/../../template-parts/config.php'; // DEMO_MAX_POSTS(公開デモの上限)
define("FILE_BBS", __DIR__ . "/bbs.log"); // ログの保存先
define("MASTER_PASSWORD", "1234"); // 削除用パスワード(デモ用の値です)
// どの API を呼び出しているか確認して、それぞれの処理へ
switch (get("api")) {
case 'list': apiList(); break;
case 'add': apiAdd(); break;
case 'delete': apiDelete(); break;
default:
outputJSON("error", "unknown api");
}
削除用のパスワード「1234」は、デモのための値です。本物のサービスでは、推測されにくいパスワードにして、コードとは別の場所(設定ファイルなど)に置いてください。
2. 書き込みを追加する
文字数を制限して受け取り、今のデータの最後に追加して保存します。番号(id)は、今ある番号の一番大きいもの+1 にします。
// 書き込みを追加する
function apiAdd() {
$name = mb_substr(trim(get("name")), 0, 30);
$title = mb_substr(trim(get("title")), 0, 50);
$body = mb_substr(trim(get("body")), 0, 500);
if ($title === "" || $body === "") {
outputJSON("error", "タイトルと本文を入力してください");
return;
}
$log = loadLogFile();
$id = $log ? max(array_column($log, "id")) + 1 : 0; // 次の番号
$log[] = [
"id" => $id,
"name" => $name === "" ? "名無し" : $name,
"title" => $title,
"body" => $body,
"status" => "ok",
];
// 公開デモなので、古いものから捨てて件数を一定に保つ
$log = array_slice($log, -DEMO_MAX_POSTS);
if (saveLogFile($log)) {
outputJSON("ok", $id);
} else {
outputJSON("error", "ファイル書込エラー");
}
}
3. 削除する
パスワードが合っていたら、該当する書き込みの status を "del" に変えます。データそのものは消さずに「削除済み」の印をつける方法で、あとから元に戻すこともできます(論理削除といいます)。
// 書き込みを削除する(実際には消さず、status を "del" にする)
function apiDelete() {
$id = intval(get("id", -1));
$password = get("password");
if ($password !== MASTER_PASSWORD) {
outputJSON("error", "パスワードが違います");
return;
}
if ($id < 0) {
outputJSON("error", "idの不正");
return;
}
$log = loadLogFile();
foreach ($log as $i => $row) {
if ($row["id"] === $id) {
$log[$i]["status"] = "del";
}
}
$r = saveLogFile($log);
outputJSON($r ? "ok" : "error", $id);
}
4. JSON で返す・ファイルに保存する
json_encode() で PHP の配列を JSON の文字列に変えて出力します。Content-Type: application/json は「これは JSON です」とブラウザやアプリに知らせるためのヘッダーです。保存するときも同じく JSON にして、ファイルに書き込みます。
// 結果を JSON で出力する
function outputJSON($code, $data) {
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *'); // スマホアプリ(別のドメイン)からの呼び出しを許可
echo json_encode(["result" => $code, "data" => $data], JSON_UNESCAPED_UNICODE);
}
// GET パラメーターを取り出す(なければ $default)
function get($name, $default = "") {
return isset($_GET[$name]) && is_string($_GET[$name]) ? $_GET[$name] : $default;
}
// ログファイルを読み込む(JSON 形式)
function loadLogFile() {
if (!file_exists(FILE_BBS)) return [];
$log = json_decode((string)file_get_contents(FILE_BBS), true);
return is_array($log) ? $log : [];
}
// ログファイルに保存する(LOCK_EX で同時書き込みによる破損を防ぐ)
function saveLogFile($log) {
$r = file_put_contents(FILE_BBS, json_encode($log, JSON_UNESCAPED_UNICODE), LOCK_EX);
return $r !== false;
}
Access-Control-Allow-Origin: * は、「ほかのドメインのページやアプリからも、この API を呼び出してよい」という許可です。これがないと、ブラウザが安全のために別ドメインからの読み込みをブロックします(CORS というしくみ)。
5. ブラウザから API を呼び出す(index.php)
fetch(URL) で API を呼び出し、res.json() で JSON を JavaScript のデータに変換します。JSON.stringify(json, null, 2) は、見やすく字下げした文字列に戻すための書き方です。
<script>
// API を呼び出して、結果の JSON を表示する
function callApi(params) {
var query = new URLSearchParams(params).toString();
var url = 'bbs-api.php?' + query;
document.getElementById('req').textContent = url;
fetch(url)
.then(function (res) { return res.json(); }) // JSON を JavaScript のデータに変換
.then(function (json) {
document.getElementById('res').textContent = JSON.stringify(json, null, 2);
})
.catch(function () {
document.getElementById('res').textContent = '通信に失敗しました';
});
}
document.getElementById('btn-list').addEventListener('click', function () {
callApi({ api: 'list' });
});
document.getElementById('btn-add').addEventListener('click', function () {
callApi({
api: 'add',
name: document.getElementById('name').value,
title: document.getElementById('post-title').value,
body: document.getElementById('body').value
});
});
document.getElementById('btn-del').addEventListener('click', function () {
callApi({
api: 'delete',
id: document.getElementById('del-id').value,
password: document.getElementById('password').value
});
});
</script>
PHP 8.3 で書き直したポイント
- 旧版には関数名のタイプミス(
outpuJSON)があり、id が不正なときに PHP 8 ではエラーで止まっていました。 - 保存形式を PHP 独自の
serialize()から、どの言語でも読める JSON に変えました。unserialize()は、細工されたデータを読み込むと危険な場合があるためです。 - 同時に書き込まれてもファイルが壊れないよう、
file_put_contents()にLOCK_EX(書き込み中はほかの人を待たせる)を付けました。
デモで書き込んだあと「一覧を取得する」を押し、JSON の中に自分の書き込みが増えているか確かめてみましょう。削除したものは "status": "del" になっているはずです。