Webアプリケーションを運営していると、古くなった投稿や不要になったデータを削除したい場面が出てきます。データベースから特定のレコードを削除するには、SQLのDELETE文を使います。
しかし、削除処理の実装は、細心の注意を払わなければならない、非常にデリケートな作業です。安易に実装すると、誤操作や悪意のある攻撃によって、意図せず重要なデータを全て失ってしまう危険性があります。
この記事では、DELETE文の基本的な使い方と、誤削除を防ぎ、安全性を確保するための「確認画面を挟む」という必須のプロセスについて解説します。
1. DELETE文の基本と最大の注意点
DELETEは、テーブルからレコード(行)を削除するためのSQL命令です。
基本構文: DELETE FROM テーブル名 WHERE 条件;
ここで、UPDATE文と同様に絶対に忘れてはならないのがWHERE句です。もしWHERE句を付けずにDELETE FROM reviews;のようなSQLを実行してしまうと、テーブル内の全レコードが削除されてしまいます。
そのため、削除対象をidなどで一意に特定するWHERE句は、削除処理の生命線と言えます。
2. なぜ危険?リンクだけで削除する実装の問題点
初心者の方がやりがちな実装として、一覧ページに<a href="delete.php?id=5">削除</a>のようなリンクを置き、delete.phpが$_GETでIDを受け取って即座にDELETE文を実行してしまう、というものがあります。
この方法は、以下の理由から極めて危険です。
- 誤クリック: ユーザーが誤って削除リンクをクリックしただけで、データが即座に消えてしまいます。
- クローラによる誤作動: Googleなどの検索エンジンが見つけたリンクを辿ることで、意図せずデータが削除されてしまう可能性があります。
- 悪意のある攻撃(CSRF): 攻撃者が、ログイン中の管理者に悪意のあるリンク(例:
<img src="yoursite.com/delete.php?id=10">)を踏ませることで、管理者が意図しないままデータを削除させることができてしまいます。
鉄則:データの状態を変更する操作(特に削除)は、GETリクエスト(リンクをクリックするだけ)で実行してはいけません。
3. 安全な削除プロセスの実装フロー
安全な削除処理は、ユーザーに「本当に削除しますか?」と意思確認を求めるステップを挟むのが基本です。
安全なフロー: [一覧ページ] → [削除確認ページ (GET)] → [削除実行スクリプト (POST)]
Step 1: 一覧ページに「削除確認」へのリンクを設置
まず、一覧表示の各行に、削除確認ページへのリンクを設置します。
list.phpのforeachループ内:
<td>
<a href="delete_confirm.php?id=<?php echo htmlspecialchars($review['id'], ENT_QUOTES, 'UTF-8'); ?>">
削除
</a>
</td>
Step 2: 削除確認ページを作成する
delete_confirm.phpでは、削除対象の情報を表示し、本当に削除してよいかユーザーに確認を求めます。
delete_confirm.phpのサンプルコード:
<?php
// (try...catchやIDのバリデーション、DB接続処理は省略)
// $id を元に、削除対象のレビュー情報を$reviewとして取得済みと仮定
if (!$review) {
// レビューが見つからない場合の処理
exit('指定されたレビューは見つかりませんでした。');
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>レビュー削除確認</title>
</head>
<body>
<h1>レビュー削除確認</h1>
<p>以下のレビューを本当に削除しますか?</p>
<dl>
<dt>書籍名:</dt>
<dd><?php echo htmlspecialchars($review['book_title'], ENT_QUOTES, 'UTF-8'); ?></dd>
</dl>
<form action="delete_process.php" method="post">
<input type="hidden" name="id" value="<?php echo htmlspecialchars($review['id'], ENT_QUOTES, 'UTF-8'); ?>">
<button type="submit">はい、削除します</button>
</form>
<br>
<a href="list.php">いいえ、一覧に戻ります</a>
</body>
</html>
ポイント:
- 削除の意思決定は、
POSTメソッドのフォームで行います。 - どのIDを削除するかを次のスクリプトに伝えるため、
type="hidden"でidをフォームに含めます。
Step 3: 削除を実行するスクリプトを作成する
最後に、確認フォームからPOST送信されたデータを受け取り、実際にDELETE文を実行するスクリプトを作成します。
delete_process.phpのサンプルコード:
<?php
// POSTリクエスト以外は処理を中断
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit('不正なリクエストです。');
}
// (データベース接続設定 $dsn, $user, $password は定義済みと仮定)
try {
// hiddenで送られたidをバリデーションして受け取る
if (empty($_POST['id']) || !ctype_digit($_POST['id'])) {
throw new Exception('IDが不正です。');
}
$id = (int)$_POST['id'];
// データベースに接続
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// DELETE文を準備・実行
$sql = "DELETE FROM reviews WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
echo "ID: " . htmlspecialchars($id, ENT_QUOTES, 'UTF-8') . " のレビューを削除しました。";
} catch (Exception $e) {
echo "エラー発生: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
} finally {
$pdo = null;
}
?>
<br>
<a href="list.php">レビュー一覧に戻る</a>
ポイント:
- スクリプトの冒頭で
POSTリクエストかどうかをチェックし、不正なアクセスを防ぎます。 $_POSTから受け取ったidを使い、安全なプリペアドステートメントでDELETEを実行します。
まとめ
DELETE文ではWHERE句が絶対に必要。- データの削除のような重要な操作は、リンク(GET)だけで実行してはならない。
- **「確認ページ(GET)→フォーム送信(POST)→削除実行」**というフローを徹底することで、誤操作や攻撃のリスクを大幅に減らすことができる。
安全な削除処理の実装は、信頼性の高いWebアプリケーションを構築するための必須スキルです。この多段階のプロセスを必ず守るようにしましょう。
