概要と目標
投稿フォームを作成し、
記事を登録できるようになろう。
投稿フォームから新着情報をデータベースに登録する方法を学習しましょう。
投稿フォームから新着情報をデータベースに登録する方法を学習しましょう。
新着情報のタイトルや内容を入力できるフォームを作成し、
データの送信先を「add.php」にする。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>投稿フォーム</title>
</head>
<body>
<h1>投稿フォーム</h1>
<form action="add.php" method="post">
<dl>
<dt><label for="title">記事のタイトル</label></dt>
<dd>
<input type="text" id="title" name="title">
</dd>
<dt><label for="category_id">カテゴリーID</label></dt>
<dd>
<input type="text" id="category_id" name="category_id">
</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>
$_POSTで受け取ったデータをINSERT文で挿入する。
ただし、「add.php」にダイレクトでアクセスされた場合は、$_POSTにデータが存在しないので、「post.php」にリダイレクトする。
$_SERVER['REQUEST_METHOD']というスーパーグローバル変数には、
リクエスト時のメソッドが格納されている。この中に「POST」が入っていれば、
フォームの送信ボタンが押されたことを意味する。
$_SERVER['REQUEST_METHOD']が、POSTではない場合は、投稿フォームにリダイレクトする
<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// 投稿ボタンが押されたかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST') {
// ダイレクトでアクセスされた時
header('Location: post.php');
exit();
}
?>
$_POST」で受けっとたデータを挿入する処理と、<?php
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/functions.php');
// 投稿ボタンが押されたかをチェック
if ( $_SERVER['REQUEST_METHOD'] !== 'POST') {
// ダイレクトでアクセスされた時
header('Location: post.php');
exit();
}
try {
// データベースへ接続
$dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
// エラー発生時に「PDOException」という例外を投げる設定に変更
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL文の作成
$sql = 'INSERT INTO posts (title, category_id, content, created) VALUES(?, ?, ?, now())';
// ステートメント用意
$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->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>
</body>
</html>
テキストエリアでの改行は、改行コードというものが埋め込まれる。
ただし、これは、テキストエディタで改行を行っているのと同様で、
ブラウザ上では改行されない。
ブラウザ上でも改行を反映するには、改行コードを<br>に変換する必要がある。
$result['content']を出力する際、改行コードを<br>に変換する
<?php echo nl2br(h($result['content']), false); ?>
現在の投稿フォームはカテゴリを、カテゴリIDで入力しなければならない。
セレクトボックスで選択できるようにして、ユーザビリティ(使いやすさ)を向上させよう。
<?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 categories';
// SQLを実行
$stmt = $dbh->query($sql);
// 実行結果を連想配列として取得
$result = $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="add.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($result as $row) : ?>
<option value="<?php echo h($row['id']); ?>"><?php echo h($row['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>
投稿フォームの機能はこれで問題ないが、現在の仕様では外部で用意されたフォームから、
「add.php」に「POST」送信された場合でも、登録処理を行ってしまう CSRFの脆弱性がある。
<?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();
}
}
<input type="hidden">を使って、
トークンを埋め込む
<?php
// セッションの開始
session_start();
// ファイルの読み込み
require_once('../inc/config.php');
require_once('../inc/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);
// データベースとの接続を終了
$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="add.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($result as $row) : ?>
<option value="<?php echo h($row['id']); ?>"><?php echo h($row['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="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: post.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 = 'INSERT INTO posts (title, category_id, content, created) VALUES(?, ?, ?, now())';
// ステートメント用意
$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->execute();
// データベースとの接続を終了
$dbh = null;
} catch (PDOException $e) {
// 例外発生時の処理
echo 'エラー' . h($e->getMessage());
exit();
}
?>
mysqldumpコマンドなどを活用する。
データベースに保存されている値を、他の環境で使う場合はバックアップを取る。
mysqldump -u root -p mini_cms_app > backup.sql
mysql -u root -p mini_cms_app < backup.sql
完成例を参考に「tranig」フォルダ内の「index.php」と「add.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();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>カテゴリを登録</title>
</head>
<body>
<h1>カテゴリを登録</h1>
<form action="add.php" method="post">
<dl>
<dt><label for="category_name">カテゴリ名</label></dt>
<dd>
<input type="text" id="category_name" name="category_name">
</dd>
</dl>
<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 = 'INSERT INTO categories (category_name) VALUES(?)';
// ステートメント用意
$stmt = $dbh->prepare($sql);
// プレースホルダーに値をガッチャンコ
$stmt->bindValue(1, $_POST['category_name'], PDO::PARAM_STR);
// ステートメントを実行
$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>
</body>
</html>
解答例は全問題のチェックボックスが on になるとご覧いただけます。
INSERT文も
送信フォームもSQLインジェクションや、CSRFなどの
脆弱性に注意する必要がある。
INSERT文もプリペアド・ステートメントを使う