概要と目標
一度投稿した内容を、
変更できる機能を作ろう。
投稿内容を変更できる機能を作成しましょう。
投稿内容を変更できる機能を作成しましょう。
テーブルの列に「編集」ボタンを追加する。
その際、リンク先URLに記事のIDをパラメータで付加しておく。
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>タイトル</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><a href="edit.php?id=<?php echo h($row['id']); ?>">編集</a></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と一致するレコードをデータベースから取得する。
そして、それらの値を、フォームの各コントール部品の初期値として割り当てればよい。
$_GET['id']に記事IDが付加されていれば、<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// GETパラメータのチェック
if ( empty($_GET['id']) ) {
// $_GET['id'] が 空 の場合
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 = 'SELECT * FROM posts WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// 実行結果を連想配列として取得
$result = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($result);
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
form要素、各コントロール部品を作成<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// GETパラメータのチェック
if ( empty($_GET['id']) ) {
// $_GET['id'] が 空 の場合
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 = 'SELECT * FROM posts WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// 実行結果を連想配列として取得
$result = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($result);
// SQL文の作成 カテゴリの全レコードを抽出
$sql = 'SELECT * FROM categories';
// クエリの実行
$stmt = $dbh->query($sql);
// 実行結果を全件連想配列として取得
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// データベースとの接続を終了
$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>
<form action="update.php" method="post">
<dl>
<dt><label for="title">記事のタイトル</label></dt>
<dd>
<input type="text" id="title" name="title">
</dd>
<dt><label for="category_id">カテゴリー</label></dt>
<dd>
<select name="category_id" id="category_id">
<?php foreach($categories as $category) : ?>
<option value="<?php echo h($category['id']); ?>"><?php echo h($category['category_name']); ?></option>
<?php endforeach; ?>
</select>
</dd>
<dt><label for="content">記事の内容</label></dt>
<dd>
<textarea name="content" id="content" cols="30" rows="10"></textarea>
</dd>
</dl>
<p><input type="submit" value="変更"></p>
</form>
</body>
</html>
print_r($result);の部分をコメントにし、<input type="hidden">で指定する
// print_r($result);
<form action="update.php" method="post">
<dl>
<dt><label for="title">記事のタイトル</label></dt>
<dd>
<input type="text" id="title" name="title" <?php echo h($result['title']); ?>>
</dd>
<dt><label for="category_id">カテゴリー</label></dt>
<dd>
<select name="category_id" id="category_id">
<?php foreach($categories as $category) : ?>
<?php
$selected = '';
// ループで出力するカテゴリと、データベースから取得したカテゴリが一致するかチェック
if ($category['id'] == $result['category_id']) {
// 一致した場合
$selected = ' selected';
}
?>
<option value="<?php echo h($category['id']); ?>"<?php echo h($selected); ?>><?php echo h($category['category_name']); ?></option>
<?php endforeach; ?>
</select>
</dd>
<dt><label for="content">記事の内容</label></dt>
<dd>
<textarea name="content" id="content" cols="30" rows="10"><?php echo h($result['content']); ?></textarea>
</dd>
</dl>
<p><input type="hidden" name="id" value="<?php echo h($result['id']); ?>"></p>
<p><input type="submit" value="変更"></p>
</form>
UPDATE文で更新する。
編集処理はGETパラメータに付加された記事IDと一致するメッセージをUPDATE文で更新する。
<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// 変更ボタンが押されたかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== '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 = 'UPDATE posts SET title=?, category_id=?, content=? WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, $_POST['title'] , PDO::PARAM_STR);
$stmt->bindValue(2, (int)$_POST['category_id'] , PDO::PARAM_INT);
$stmt->bindValue(3, $_POST['content'] , PDO::PARAM_STR);
$stmt->bindValue(4, (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>
現在の仕様では、外部フォームから「update.php」にデータを送信されても更新する
このままでは、CSRFの脆弱性があるので対策する。
SESSIONを開始し、独自関数を使ってトークンを生成<input type="hidden">でフォームに埋め込む
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// GETパラメータのチェック
if ( empty($_GET['id']) ) {
// $_GET['id'] が 空 の場合
header('Location: index.php');
exit();
}
// 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 posts WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// 実行結果を連想配列として取得
$result = $stmt->fetch(PDO::FETCH_ASSOC);
// print_r($result);
// SQL文の作成 カテゴリの全レコードを抽出
$sql = 'SELECT * FROM categories';
// クエリの実行
$stmt = $dbh->query($sql);
// 実行結果を全件連想配列として取得
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// データベースとの接続を終了
$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>
<form action="update.php" method="post">
<dl>
<dt><label for="title">記事のタイトル</label></dt>
<dd>
<input type="text" id="title" name="title" value="<?php echo h($result['title']); ?>">
</dd>
<dt><label for="category_id">カテゴリー</label></dt>
<dd>
<select name="category_id" id="category_id">
<?php foreach($categories as $category) : ?>
<?php
$selected = '';
// ループで出力するカテゴリと、データベースから取得したカテゴリが一致するかチェック
if ($category['id'] == $result['category_id']) {
// 一致した場合
$selected = ' selected';
}
?>
<option value="<?php echo h($category['id']); ?>"<?php echo h($selected); ?>><?php echo h($category['category_name']); ?></option>
<?php endforeach; ?>
</select>
</dd>
<dt><label for="content">記事の内容</label></dt>
<dd>
<textarea name="content" id="content" cols="30" rows="10"><?php echo h($result['content']); ?></textarea>
</dd>
</dl>
<p><input type="hidden" name="id" value="<?php echo h($result['id']); ?>"></p>
<p><input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>"></p>
<p><input type="submit" value="変更"></p>
</form>
</body>
</html>
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// 変更ボタンが押されたかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST') {
// ダイレクトでアクセスされた時
header('Location: index.php');
exit();
}
// CSRF対策 ・・・ トークンのチェック
check_token();
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'UPDATE posts SET title=?, category_id=?, content=? WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, $_POST['title'] , PDO::PARAM_STR);
$stmt->bindValue(2, (int)$_POST['category_id'] , PDO::PARAM_INT);
$stmt->bindValue(3, $_POST['content'] , PDO::PARAM_STR);
$stmt->bindValue(4, (int)$_POST['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
完成例を参考に「tranig」フォルダ内にある「index.php」、「edit.php」、「update.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
// ファイルの読み込み
require_once('config.php');
require_once('functions.php');
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><a href="edit.php?id=<?php echo h($row['id']); ?>">編集</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('config.php');
require_once('functions.php');
// GETパラメータのチェック
if ( empty($_GET['id']) ) {
// $_GET['id'] が 空 の場合
header('Location: index.php');
exit();
}
// 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 WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);
// ステートメントを実行
$stmt->execute();
// 実行結果を連想配列として取得
$result = $stmt->fetch(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>
<form action="update.php" method="post">
<dl>
<dt><label for="category_name">カテゴリ名</label></dt>
<dd>
<input type="text" id="category_name" name="category_name" value="<?php echo h($result['category_name']); ?>">
</dd>
</dl>
<p><input type="hidden" name="id" value="<?php echo h($result['id']); ?>"></p>
<p><input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>"></p>
<p><input type="submit" value="変更"></p>
</form>
</body>
</html>
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('config.php');
require_once('functions.php');
// 変更ボタンが押されたかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST') {
// ダイレクトでアクセスされた時
header('Location: index.php');
exit();
}
// CSRF対策 ・・・ トークンのチェック
check_token();
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'UPDATE categories SET category_name=? WHERE id = ?';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, $_POST['category_name'] , PDO::PARAM_STR);
$stmt->bindValue(2, (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 になるとご覧いただけます。
UPDATE文でレコードを更新する。
編集機能は記事のIDをパラメータに付加し、そのIDと一致するレコードを取得。
取得したレコードをフォームの初期値に割り当てて、UPDATE文で更新する。
UPDATE文を使う