概要と目標
管理画面を作成し、
記事を削除できる機能を作ろう。
管理画面に記事の一覧を表示し、記事を削除できるようになりましょう。
管理画面に記事の一覧を表示し、記事を削除できるようになりましょう。
サイトの管理者が記事を管理するためのページを作成。
talbe要素で表示
<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'SELECT * FROM posts';
// SQLを実行
$stmt = $dbh->query($sql);
// 実行結果を連想配列として取得
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
// print_r($result);
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>管理画面</title>
</head>
<body>
<h1>管理画面</h1>
<p><a href="post.php">新しい記事を投稿する</a></p>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>タイトル</th>
<th>公開日</th>
<th>更新日</th>
</tr>
</thead>
<tbody>
<?php foreach($result as $row) : ?>
<tr>
<td><?php echo h($row['id']); ?></td>
<td><?php echo h($row['title']); ?></td>
<td><time datetime="<?php echo h($row['created']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['created']))); ?></time></td>
<td><time datetime="<?php echo h($row['modified']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['modified']))); ?></time></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
テーブルの列に「削除」ボタンを追加する。
その際、form要素内に記事のIDをinput要素のhidden属性で埋め込んでおく。
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>タイトル</th>
<th>公開日</th>
<th>更新日</th>
<th>削除</th>
</tr>
</thead>
<tbody>
<?php foreach($result as $row) : ?>
<tr>
<td><?php echo h($row['id']); ?></td>
<td><?php echo h($row['title']); ?></td>
<td><time datetime="<?php echo h($row['created']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['created']))); ?></time></td>
<td><time datetime="<?php echo h($row['modified']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['modified']))); ?></time></td>
<td>
<form action="delete.php" method="post">
<input type="hidden" name="id" value="<?php echo h($row['id']); ?>">
<input type="submit" value="削除">
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
削除処理は隠しフィールドで送信した記事IDと一致するメッセージをDELETE文で削除する。
<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// POSTかダイレクトかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
// POSTじゃない場合
header('Location: index.php');
exit();
}
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'DELETE FROM posts WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, (int)$_POST['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>記事の削除</title>
</head>
<body>
<h1>記事の削除</h1>
<p>記事を削除しました。</p>
<p><a href="./">一覧に戻る</a></p>
</body>
</html>
このままでは、外部のフォームからでも記事を削除できてしまう。
外部のフォームから削除されないように、CSRF対策を行う。
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// CSRF対策 ・・・ トークンの生成
set_token();
<input type="hidden">を使って、
トークンを埋め込む
<tbody>
<?php foreach($result as $row) : ?>
<tr>
<td><?php echo h($row['id']); ?></td>
<td><?php echo h($row['title']); ?></td>
<td><time datetime="<?php echo h($row['created']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['created']))); ?></time></td>
<td><time datetime="<?php echo h($row['modified']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['modified']))); ?></time></td>
<td>
<form action="delete.php" method="post">
<input type="hidden" name="id" value="<?php echo h($row['id']); ?>">
<input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>">
<input type="submit" value="削除">
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// POSTかダイレクトかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
// POSTじゃない場合
header('Location: index.php');
exit();
}
// CSFR対策 ・・・ トークンを確認
check_token();
完成例を参考に「tranig」フォルダ内の「index.php」と「delete.php」を使って
カテゴリの管理画面と、カテゴリ削除機能を作成して下さい。
<?php
// データベースの定数
define('DB_NAME', 'mini_cms_app');
define('DB_USER', 'root');
define('DB_PASSWORD', ''); // パスワード (MAMPは「root」)
define('DSN', 'mysql:host=localhost;dbname='. DB_NAME . ';charset=utf8');
<?php
// XSS 対策
function h($s) {
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// CSRF対策 トークンの生成
function set_token() {
if (!isset($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
}
}
// CSRF対策 トークンの確認
function check_token() {
if (empty($_POST['token']) || $_POST['token'] != $_SESSION['token']) {
echo '不正な投稿(トークンが一致しません。)';
exit();
}
}
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('config.php');
require_once('functions.php');
// CSRF対策 ・・・ トークンの生成
set_token();
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'SELECT * FROM categories';
// SQLを実行
$stmt = $dbh->query($sql);
// 実行結果を連想配列として取得
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
// print_r($result);
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>カテゴリの管理</title>
</head>
<body>
<h1>カテゴリの管理</h1>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>カテゴリ名</th>
<th>削除</th>
</tr>
</thead>
<tbody>
<?php foreach($result as $row) : ?>
<tr>
<td><?php echo h($row['id']); ?></td>
<td><?php echo h($row['category_name']); ?></td>
<td>
<form action="delete.php" method="post">
<input type="hidden" name="id" value="<?php echo h($row['id']); ?>">
<input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>">
<input type="submit" value="削除">
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('config.php');
require_once('functions.php');
// POSTかダイレクトかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
// POSTじゃない場合
header('Location: index.php');
exit();
}
// CSFR対策 ・・・ トークンを確認
check_token();
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'DELETE FROM categories WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, (int)$_POST['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>カテゴリの削除</title>
</head>
<body>
<h1>カテゴリの削除</h1>
<p>カテゴリを削除しました。</p>
<p><a href="./">一覧に戻る</a></p>
</body>
</html>
解答例は全問題のチェックボックスが on になるとご覧いただけます。
DELETE文。
削除機能は記事のIDを隠しフィールドに埋め込んで、DELETE文で削除する。
DELETE文