ひとこと掲示板のようなものを作りたい
準備したデータベース(min_bbs_02で作成)
▼members
▼posts
項目を入力しなければエラーが起こる処理を作成
- 作りたい動き
- index.phpで入力・内容チェック
- 正しければcheck.phpで確認を行う。データベースにも登録を行う
- thank.phpの画面に移動する
index.php
<form action="" method="post" enctype="multipart/form-data">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd>
<input type="text" name="name" size="35" maxlength="255" value=""/>
<p class="error">* ニックネームを入力してください</p>
</dd>
<dt>メールアドレス<span class="required">必須</span></dt>
<dd>
<input type="text" name="email" size="35" maxlength="255" value=""/>
<p class="error">* メールアドレスを入力してください</p>
<p class="error">* 指定されたメールアドレスはすでに登録されています</p>
<dt>パスワード<span class="required">必須</span></dt>
<dd>
<input type="password" name="password" size="10" maxlength="20" value=""/>
<p class="error">* パスワードを入力してください</p>
<p class="error">* パスワードは4文字以上で入力してください</p>
</dd>
<dt>写真など</dt>
<dd>
<input type="file" name="image" size="35" value=""/>
<p class="error">* 写真などは「.png」または「.jpg」の画像を指定してください</p>
<p class="error">* 恐れ入りますが、画像を改めて指定してください</p>
</dd>
</dl>
<div><input type="submit" value="入力内容を確認する"/></div>
</form>
-
<form action="" method="post" enctype="multipart/form-data">
- postで渡している
- 写真をアップロードする動きがあるので
enctype="multipart/form-data"
の記述あり -
action=""
はなぜ空なのか?- 前の項目では
action="input_do.php"
などで遷移させていた - ここを空にすると自分自身(inde.php)はもう一度呼ばれる形になる
- なぜなのか→エラー画面を同じ画面に表示させたいから!
- 前の項目では
ニックネーム欄を作成していく
<?php
$form = [];
$error = [];
$form['name'] = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
if($form['name'] === ''){
$error['name'] = 'blank';
}
?>
- ↑$form['name']の中身が空なら
$error['name']
にblank
を代入 - 何回も出ていているが解説、、
▼$form['name'] = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
↑↑↑filter_input→POST や GET などの変数をフィルタリングする -
INPUT_POST
→POSTの値を受け取る -
'name'
→nameが「name」に設定されている値を受け取る→name="name"
の状態のinput
filter_input内の「name」はここで指定されているname
↓↓↓
<input type="text" name="name" size="35" maxlength="255" value=""/>
- FILTER_SANITIZE_STRING→文字列しか受け取れないようにする処理
<dd>
<input type="text" name="name" size="35" maxlength="255" value=""/>
<?php if(isset($error['name']) && $error['name'] === 'blank'): ?>
<p class="error">* ニックネームを入力してください</p>
<?php endif; ?>
</dd>
-
$error['name']
がblank
なら「* ニックネームを入力してください」のエラー文を表示 -
isset($error['name'])
の条件に関しては講座上で表示エラーが出ているため。
自分の作業中は特に出ず…
一度記入した内容に関してはページ更新後もそのまま表示させたい
※ニックネームはチェックOKなのに、アドレスはエラーだった場合、ニックネームの値は残ってほしい
<dd>
<input type="text" name="name" size="35" maxlength="255" value="<?php echo htmlspecialchars($form['name'], ENT_QUOTES); ?>"/>
<p class="error">* ニックネームを入力してください</p>
<?php endif; ?>
</dd>
-
valueは記入しておくとテキストフィールドにもともと値が入る、という属性
-
value="<?php echo htmlspecialchars($form['name'], ENT_QUOTES); ?>"
valueにこちらの記述を追加。ニックネームを入力した内容である$form['name']
。 -
ENT_QUOTESとは??↓↓↓
『ENT_QUOTES』という定数。 ENT_QUOTESはPHPが定数としてもっているint型の値であり、ENT_QUOTESを指定すると、特殊文字のうちシングルクォーテーションとダブルクォーテーションも変換対象に含めるようになります。 ENT_QUOTESの他には以下のような定数が用意されており、状況に応じて使い分けることが可能です。
ENT_COMPAT(ダブルクォーテーションは変換するがシングルクォーテーションは変換しない)
ENT_NOQUOTES(シングルクォーテーションとダブルクォーテーションを共に変換しない)
ENT_HTML5(コードをHTML5として扱う)
このように、htmlspecialchars() を利用すれば特殊文字を簡単にHTMLエンティティに変換することが出来ます。
微修正作業
内容の送信をした場合のみチェックを追加する
/* フォームの内容をチェック */
if ($_SERVER[['REQUEST_METHOD'] === 'POST']) {
$form['name'] = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
if ($form['name'] === '') {
$error['name'] = 'blank';
}
}
if ($_SERVER[['REQUEST_METHOD'] === 'POST']) {
- このif文に入れてあげる
- 「フォームからの投稿などのとき」のような条件になる
配列の初期化
$form = [
'name' => ''
];
- ここが
$form = [];
だとエラーが出るようになってしまっていた <input type="text" name="name" size="35" maxlength="255" value="<?php echo htmlspecialchars($form['name'], ENT_QUOTES); ?>"/>
- ↑ここで
$form['name']
を使おうとするが、最初はなにも値が入っていないため - formの配列はnameというもので使っていきます、と初期化をしてあげるとうまくいく
-
<input type="text" name="name" size="35" maxlength="255" value="<?php echo htmlspecialchars($form['name'], ENT_QUOTES); ?>"/>
↑この中の$form['name']に何もない状態ではなく''
の状態にしてあげたのだと思う
htmlspecialcharsを短くする
/* htmlspecialcharsを短くする */
function h($value){
return htmlspecialchars($value, ENT_QUOTES);
}
<input type="text" name="name" size="35" maxlength="255" value="<?php echo h($form['name']); ?>" />
↑このように省略できる
メールアドレス・パスワードの入力欄を作成
$form = [
'name' => '',
'email' => '',
'password' => '',
];
$error = [];
/* フォームの内容をチェック */
if ($_SERVER[['REQUEST_METHOD'] === 'POST']) {
$form['name'] = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
if ($form['name'] === '') {
$error['name'] = 'blank';
}
$form['email'] = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
if($form['email'] === ''){
$error['email'] = 'blank';
}
$form['password'] = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
if($form['password'] === ''){
$error['password'] = 'blank';
}else if(strlen($form['password']) < 4){
$error['password'] = 'length';
}
}
- フォーム内容チェック欄にメール、パスワードの項目も同様に追加
パスワード文字数の制御
$form['password'] = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
if($form['password'] === ''){
$error['password'] = 'blank';
}else if(strlen($form['password']) < 4){
$error['password'] = 'length';
}
- それがこちら
else if(strlen($form['password']) < 4)
- この条件で文字の条件を追加している
-
strlen
→文字列の長さ
画像の入力を確認
// 画像のチェック
$image = $_FILES['image'];
if ($image['name'] !== '' && $image['error'] === 0){
$type = mime_content_type($image['tmp_name']);
if ($type !== 'image/png' && $type !== 'image/jpeg'){
$error['image'] = 'type';
}
}
-
$image = $_FILES['image'];
↑こちらにinputの内容が入ってくる
<input type="file" name="image" size="35" value="" />
-
$image['name']
↑$image
は$image = $_FILES['image'];
で代入した内容が色々入っている
その中の$image['name']
はファイル名が入っている -
$image['name'] !== ''
画像入力欄の内容が空でないか確認する -
$image['error'] === 0
エラーコードが出ていなければ -
mime_content_type()
ファイルの形式を判別してくれるファンクション
$image['tmp_name']
とは?テンポラリーファイルのことでphpが一時的につけるファイル名のこと
if ($type !== 'image/png' && $type !== 'image/jpeg'){
$error['image'] = 'type';
}
- こちらでpngとjpgのみを受け入れる処理にしている
- errorコードにtyoeが入るように設定(自分が作った際は特に反映されず)
<?php if (isset($error['image']) && $error['image'] === 'type'): ?>
<p class="error">* 写真などは「.png」または「.jpg」の画像を指定してください</p>
<?php endif; ?>
こちらで表示上の制御文を設定している
フォームの送信
if(empty($error)){
header('Location: check.php');
exit();
}
-
$error
という配列に何も入っていなければ次の画面に進んでよい、という条件にしている。 -
header('Location: check.php');
これは「check.php」へのリダイレクト文 -
exit();
プログラムの終了。これはなぜいるのか?
画面の遷移に関して
header('Location: check.php');
で移動しているため、
index.phpからcheck.phpに移動すると、データはすべて状態となる。
対応
- セッションを使って、フォーム入力した内容を使ってデータを読み込む
<?php
session_start();
- こちら頭に記述することでセッションが使えるようになる
if(empty($error)){
$_SESSION['form'] = $form;
header('Location: check.php');
exit();
}
-
$_SESSION['form'] = $form;
を追加 -
$form
の中にはこれまでname``email``password
など入力してきた情報が入っているので、
そちらをセッションに保存する - このようにまとめてセッションに保存できるので、各入力要素をform配列に格納していたということ
ここまででデータがうまく移動できているかチェック
- ニックネームなどを適当に入力して、「入力内容を確認する」を押す
-
check.php
に飛ぶ。保存した配列もvar_dumpでちゃんと表示されている!
画像のアップロード処理について
- ここまではあくまで画像の内容チェック処理のみだけで、アップロード処理は無い状態
if(empty($error)){
$_SESSION['form'] = $form;
// 画像のアップロード
$filename = date('YmdHis') . '_' . $image['name'];
var_dump($filename);
exit();
header('Location: check.php');
exit();
}
- アップロード処理を追加してみて、入力完了を押してみる
- こんな感じでファイル名に日付が付いた状態になっている
- 同じファイル名になっても重複しないようにしている
-
date('YmdHis') . '_' . $image['name'];
ここの処理です↑↑↑ - ほんとはもっとデータベースと連携してidつけるなどの処理がいるが、いったん簡易的に実装している
ちゃんと処理を追加すると下記のようになる
if(empty($error)){
$_SESSION['form'] = $form;
// 画像のアップロード
$filename = date('YmdHis') . '_' . $image['name'];
move_uploaded_file($image['tmp_name'], '../member_picture/' . $filename);
header('Location: check.php');
exit();
}
-
move_uploaded_file()
ファイルのアップロードを使った時に、そのアップロードされたファイルをテンポラリー(一時的)な
場所から正式な場所に移動する処理 -
1つ目の名前がテンポラリーの名前
-
2つ目が正式な場所
-
1つ目→
$image['tmp_name']
から -
2つ目→
'../member_picture/' . $filename
に移動
という感じ -
'../member_picture/' . $filename
↑こちら階層上の「member_picture」のフォルダに保存なので、新規フォルダで「member_picture」を作った
ファイルの権限エラーの対策を加えておく
// 画像のアップロード
if($image['name'] !== ''){
$filename = date('YmdHis') . '_' . $image['name'];
if(!move_uploaded_file($image['tmp_name'], '../member_picture/' . $filename);){
die('ファイルのアップロードに失敗しました');
}
$_SESSION['form']['image'] = $filename;
}else{
$_SESSION['form']['image'] = '';
}
-
if(!move_uploaded_file($image['tmp_name'], '../member_picture/' . $filename);){
↑ここがfalseならエラー文出るようにする - filenameもセッションに記録する処理を追加
$_SESSION['form']['image'] = $filename;
↑↑↑['form']の中にある['image']を$filenameに書き換える
画像が指定されていなかった場合の処理も追加している
-
if($image['name'] !== ''){
↑ここで大枠囲うことで画像の有り無しを判定 -
elseの場合も追加
→$_SESSION['form']['image'] = '';
画像の指定が無い場合、['form']の中の['image']を空で設定
- これで画像をまたアップしてみると、画像ファイルの名前がcheck.phpに渡ってきていることがわかる
mime_content_typeは現在非推奨となっている
- php7の後半、php8などで非推奨
Fileinfo・finfoを用いる
<?php
$finfo = new finfo();
$mime = $finfo->file('MANP-PRO-LOGO.png', FILEINFO_MIME_TYPE);
echo $mime;
-
$finfo->file('MANP-PRO-LOGO.png', FILEINFO_MIME_TYPE);
1つ目がファイルのパス
2つ目が「何を調べるか」MIMETYPEを調べてください、という処理になっている
- pngファイルであることがわかる
<?php
$finfo = new finfo();
$mime = $finfo->file('index.php', FILEINFO_MIME_TYPE);
echo $mime;
確認画面の作成
<?php
session_start();
$form = $_SESSION['form'];
?>
<!DOCTYPE html>
- 頭に
$form = $_SESSION['form'];
を追加する - index.phpからセッションで受け取った['form']の値を格納
<dl>
<dt>ニックネーム</dt>
<dd><?php echo $form['name']; ?></dd>
<dt>メールアドレス</dt>
<dd>info@example.com</dd>
<form action="" method="post">
<dl>
<dt>ニックネーム</dt>
<dd><?php echo h($form['name']); ?></dd>
<dt>メールアドレス</dt>
<dd><?php echo($form['email']); ?></dd>
<dt>パスワード</dt>
<dd>
【表示されません】
</dd>
<dt>写真など</dt>
<dd>
<img src="../member_picture/<?php echo h($form['image']); ?>" width="100" alt="" />
</dd>
</dl>
<div><a href="index.php?action=rewrite">« 書き直す</a> | <input type="submit" value="登録する" /></div>
</form>
- メールアドレス、画像に関しても同様に追加すると確認画面に表示される
いきなり確認画面(check.php)にアクセスしようとした際の対策
<?php
session_start();
require('../library.php');
if(isset($_SESSION['form'])){
$form = $_SESSION['form'];
}else{
header('Location: index.php');
exit();
}
?>
- check.phpの頭にif文を追記する
-
if(isset($_SESSION['form'])){
→formの値がセットされていたら、という条件 - trueならもともとの処理と同様に変数に格納。falseならindex.phpに飛ばす
入力した内容をデータベースに連携して登録させる
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$db = dbconnect();
$stmt = $db->prepare('insert into members (name, email, password, picture) VALUES (?, ?, ?, ?)');
if (!$stmt) {
die($db->error);
}
$password = password_hash($form['password'], PASSWORD_DEFAULT);
$stmt->bind_param('ssss', $form['name'], $form['email'], $password, $form['image']);
$success = $stmt->execute();
if (!$success) {
die($db->error);
}
unset($_SESSION['form']);
header('Location: thanks.php');
}
?>
パスワードの登録を暗号化する必要あり
- abcdがデータベース上に表示されているのは危険
- パスワードはデータベース上に直接登録しないようにする
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$db = new mysqli('localhost:8889', 'root', 'root', 'min_bbs_02');
if(!$db){
die($db->error);
}
$stmt = $db->prepare('insert into members (name, email, password, picture) VALUES (?, ?, ?, ?)');
if(!$stmt){
die($db->error);
}
$password = password_hash($form['password'], PASSWORD_DEFAULT);
$stmt->bind_param('ssss', $form['name'], $form['email'], $password, $form['image']);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
unset($_SESSION['form']);
header('Location: thanks.php')
}
?>
$password = password_hash($form['password'], PASSWORD_DEFAULT);
$stmt->bind_param('ssss', $form['name'], $form['email'], $password, $form['image']);
- こちらでパスワードを暗号化する
unset($_SESSION['form']);
header('Location: thanks.php');
- 処理が終わったらthanksページに飛ばしたいのでunsetでセッションの情報を落としてから、
thanks.phpに移動させる
入力内容のやり直しができるように
- 確認画面の「書き直す」を押すと入力画面に値を残したまま戻れるようにしたい
<div><a href="index.php?action=rewrite">« 書き直す</a> | <input type="submit" value="登録する" /></div>
</form>
</div>
- check.phpの書き直すリンクに追記
- パラメータを付けている
<?php
session_start();
require('../library.php');
if(isset($_GET['action']) && $_GET['action'] === 'rewrite' && isset($_SESSION['form'])){
$form = $_SESSION['form'];
}else{
$form = [
'name' => '',
'email' => '',
'password' => '',
];
}
$error = [];
- check.phpで追加したパラメータが付いていたら、ページに戻っても入力内容を再現する、というプログラムを追加している
-
if(isset($_GET['action']) && $_GET['action'] === 'rewrite' && isset($_SESSION['form'])){
ここの条件です
メールアドレスの重複チェックを修正していく
まずはデータベースへの接続を共通ファイルに移行していく
<?php
/* htmlspecialcharsを短くする */
function h($value){
return htmlspecialchars($value, ENT_QUOTES);
}
/* DBへの接続 */
function dbconnect(){
$db = new mysqli('localhost:8889', 'root', 'root', 'min_bbs_02');
if (!$db) {
die($db->error);
}
return $db;
}
?>
- ↑↑↑library.phpに追記
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$db = dbconnect();
$stmt = $db->prepare('insert into members (name, email, password, picture) VALUES (?, ?, ?, ?)');
if (!$stmt) {
die($db->error);
}
$password = password_hash($form['password'], PASSWORD_DEFAULT);
$stmt->bind_param('ssss', $form['name'], $form['email'], $password, $form['image']);
$success = $stmt->execute();
if (!$success) {
die($db->error);
}
unset($_SESSION['form']);
header('Location: thanks.php');
}
- ↑↑↑check.phpにも追記
- 接続情報を記述していたところに関数を記述
$db = dbconnect();
ログイン画面を作る
- login.phpがあらかじめ準備されている
- こんな画面なので、昨日を追加していく
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST'){
echo 'submit';
}
?>
- login.phpの頭にこちら追加してみる
- 「ログインする」ボタン押してみるとそれだけで「sumit」echoで表示される
-
if($_SERVER['REQUEST_METHOD'] === 'POST'){
→POSTが実行されたら、、という意味
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
var_dump($email);
}
?>
- ifの中に条件を追加してみる
-
filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
↑↑↑filter_input→POST や GET などの変数をフィルタリングする -
var_dump($email);
で表示してみる
- メール欄に適当に入力してボタン押すとechoで'$email'の変数が表示される。
<?php
$error = [];
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
if($email === '' || $password === ''){
$error['login'] = 'blank';
}
}
?>
- もし中身なかった場合の条件を追加する
- 配列にエラーの内容を入れることで制御がしやすくなるため
- 頭で
$error = [];
で配列初期化
入力した値をボタン押しても維持するように設定
<?php
require('library.php');
$error = [];
$email = '';
$password = '';
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
if($email === '' || $password === ''){
$error['login'] = 'blank';
}
}
?>
-
require('library.php');
→こちらで処理読み込み
→中身は「htmlspecialchars」の設定と、dbの接続 -
$email = '';
、$password = '';
で各値初期化
<div id="content">
<div id="lead">
<?php if(isset($error['login']) && $error['login'] === 'blank'): ?>
<p>メールアドレスとパスワードを記入してログインしてください。</p>
<?php endif; ?>
<p>入会手続きがまだの方はこちらからどうぞ。</p>
<p>»<a href="join/">入会手続きをする</a></p>
</div>
-
<?php if(isset($error['login']) && $error['login'] === 'blank'): ?>
→メールアドレスの記入がない場合、の条件で記入してくださいのエラー文出す
<dl>
<dt>メールアドレス</dt>
<dd>
<input type="text" name="email" size="35" maxlength="255" value="<?php echo h($email); ?>"/>
<p class="error">* メールアドレスとパスワードをご記入ください</p>
<p class="error">* ログインに失敗しました。正しくご記入ください。</p>
</dd>
<dt>パスワード</dt>
<dd>
<input type="password" name="password" size="35" maxlength="255" value="<?php echo h($password); ?>"/>
</dd>
</dl>
- 各入力欄の
value
の値に一度入力した内容が入るように処理追加 <input type="text" name="email" size="35" maxlength="255" value="<?php echo h($email); ?>"/>
<input type="password" name="password" size="35" maxlength="255" value="<?php echo h($password); ?>"/>
ログインの実際の仕組みを作る
<?php
require('library.php');
$error = [];
$email = '';
$password = '';
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
if($email === '' || $password === ''){
$error['login'] = 'blank';
}else{
// ログインチェック
$db = dbconnect();
$stmt = $db->prepare('select id, name, password from members where email=? limit 1');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('s', $email);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
$stmt->bind_result($id, $name, $hash);
$stmt->fetch();
var_dump($hash);
}
}
?>
- ログイン画面で入力した値がデータベースに登録したログイン情報と一致しているかを確認している
- ログインチェックSQL文の
limit 1
はセキュリティの意味合いで入れている -
$stmt->bind_param('s', $email);
→なぜpasswordも含まずemailだけなのか。
→入力欄で受け取っているパスワードと、データベースに記録されているパスワード(ハッシュ化されたもの)が異なるので、ここはemailのみでよい
まずはメールアドレスだけ受け取って、passwordが合っているかを確認する、という手順を踏んでいる -
$stmt->bind_result($id, $name, $hash);
→$hash
ここはなぜ$password
じゃないのか。
→ 同じ変数が上部の入力側passwordの変数として使われているため - 一致していたら
var_dump($hash);
でハッシュ化されたパスワードを表示してみている
- 表示される
ハッシュ化されたパスワードと、ユーザーが入力したパスワードが合っているかを確認する
<?php
session_start();
require('library.php');
$error = [];
$email = '';
$password = '';
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
if($email === '' || $password === ''){
$error['login'] = 'blank';
}else{
// ログインチェック
$db = dbconnect();
$stmt = $db->prepare('select id, name, password from members where email=? limit 1');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('s', $email);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
$stmt->bind_result($id, $name, $hash);
$stmt->fetch();
if(password_verify($password, $hash)){
//ログイン成功
session_regenerate_id();
$_SESSION['id'] = $id;
$SESSION['name'] = $name;
header('Location: index.php');
exit();
}else{
$error['login'] = 'failed';
}
}
}
?>
- 専用のファンクションを使う
-
password_verify()
パスワードと、ハッシュ化されたパスワードを指定すると一致しているかを確認してくれる -
if(password_verify($password, $hash)){
入力欄のパスワードと、データーベース上のパスワードが一致していたら、という条件
ログイン失敗時の処理
}else{
$error['login'] = 'failed';
}
<?php if(isset($error['login']) && $error['login'] === 'failed'): ?>
<p class="error">* ログインに失敗しました。正しくご記入ください。</p>
<?php endif; ?>
- これだけでログイン失敗時にエラーに配列を加えることで、表示切替の条件式にしている
ログイン成功時の処理
- ログインの成功時にはlogin.php→index.phpに飛ぶ
- 飛び先のページの名前表示欄にログインした人の名前が出ていればOK
<?php
session_start();
- まずはlogin.phpの頭に
session_start();
加える
if(password_verify($password, $hash)){
//ログイン成功
session_regenerate_id();
$_SESSION['id'] = $id;
$SESSION['name'] = $name;
header('Location: index.php');
exit();
}else{
$error['login'] = 'failed';
}
- session_regenerate_id();
→セッションを使う時に作法として入れるとよい
session_idを生成しなおすもの
安全性のためにやっている
(session_idをあまり長い時間使っていると、セッション情報が盗まれるリスクが高くなる)
session_regenerate_id();を使ってsession_idを再生成している
$stmt->bind_result($id, $name, $hash);
$stmt->fetch();
if(password_verify($password, $hash)){
//ログイン成功
session_regenerate_id();
$_SESSION['id'] = $id;
$_SESSION['name'] = $name;
header('Location: index.php');
exit();
}else{
$error['login'] = 'failed';
}
}
$_SESSION['id'] = $id;
-
$_SESSION['name'] = $name;
こちらは下記のidとnameをセッションに保存している(上部のやつです)
↓↓↓
$stmt->bind_result($id, $name, $hash);
header('Location: index.php');
exit();
- index.phpにジャンプさせて処理を終える
- ここまで処理追加すればlogin.phpでの記述は完了、送ったデータをindex.php側で表示できるようにする
index.phpに内容追加
<?php
session_start();
require('library.php');
$name = $_SESSION['name'];
?>
- まずはセッションの開始と、関数使いたいので共通ファイル読み込み
- セッション変数をそのまま使うのは処理としてスマートではないので、通常の変数
$name
に格納する
<dt><?php echo h($name); ?>さん、メッセージをどうぞ</dt>
- 共通ファイルの関数利用して、セッションで保存された
$name
を表示させてみる
- ログイン登録の際に入力した名前「テストくん」が表示されていることを確認
もしログインしていない状態ならページ自体表示できないような処理を追加したい
<?php
session_start();
require('library.php');
$name = $_SESSION['name'];
if(isset($_SESSION['id']) && isset($_SESSION['name'])){
$name = $_SESSION['name'];
}else{
header('Location: login.php');
exit();
}
?>
- セッションでidとnameがセットされていればセッションのnameを変数に格納
- そうでなければlogin.phpに飛ばす
という処理を追加する
これでちゃんとログインできていない状態だとindex.phpには入れないように設定できている
一覧画面(メイン画面)を作る
- こちらの画面から投稿すると、一覧画面にそれぞれの投稿が表示されるような画面を作る
// メッセージの投稿
if($_SEVER['REQUEST_METHOD'] === 'POST'){
$message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING);
$db = dbconnect();
$stmt = $db->prepare('insert into posts (message, member_id) values(?,?)');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('si', $message, $id);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
}
-
index.phpに投稿に関する内容を追加
-
if ($_SERVER[['REQUEST_METHOD'] === 'POST']) {
このif文は「フォームからの投稿があったとき」のような条件 -
$message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING);
↑↑↑filter_input→POST や GET などの変数をフィルタリングする
FILTER_SANITIZE_STRING→文字列しか受け取れないようにする処理 -
↑↑↑【大注意!!】不要なカラムを最初に追加しているせいでうまく処理が通らなかった
-
「replay_post_id」のカラムはphpmyadminで削除した上で処理を行ってみる
![240317_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655480/b083c48e-f929-adf4-3b33-709c4d964dee.png
ページ再読み込みすると再度投稿が送信されてしまうので修正する
$stmt->bind_param('si', $message, $id);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
header('Location: index.php');
exit();
}
- ページ末尾に同じページに遷移する処理を書く
- それだけでアラートボックスは出てこなくなる(POSTの内容を無くしている)
一覧画面の仕組みを作る
- ひとまず投稿をデータベースに入れるところまではできたので、それを表示させていきたい
$db = dbconnect();
// メッセージの投稿
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING);
-
$db = dbconnect();
if文の中にあったデータベースへの接続は流用したいのでif文外に出す
<?php $stmt = $db->prepare('select p.id, p.member_id, p.message, p.created,
m.name, m.picture from posts p, members m
where m.id=p.member_id order by id desc');
if(!$stmt){
die($db->error);
}
$success = $stmt->execute();
if(!$success){
die($db->error);
}
$stmt->bind_result($id, $member_id, $message, $created, $name, $picture);
while($stmt->fetch()):
?>
<div class="msg">
<?php if($picture): ?>
<img src="member_picture/<?php echo h($picture); ?>" width="48" height="48" alt=""/>
<?php endif; ?>
<p><?php echo h($message); ?><span class="name">(<?php echo h($name); ?>)</span></p>
<p class="day"><a href="view.php?id="><?php echo h($created); ?></a>
[<a href="delete.php?id=" style="color: #F33;">削除</a>]
</p>
</div>
<?php endwhile; ?>
</div>
</div>
</body>
</html>
- ページ下部の方につらつらと書く
$stmt = $db->prepare('select p.id, p.member_id, p.message, p.created,
m.name, m.picture from posts p, members m
where m.id=p.member_id order by id desc');
- この時点でむずすぎる
-
from posts p, members m
とあるように
p.id
はpostsのid、m.name
はmembersのnameとなる -
select p.id, p.member_id, p.message, p.created, m.name, m.picture
↑↑↑「postsのid」「postsのmember_id」「postsのmessage」「postsのcreated」
「membersのname」「membersのpicture」を表示させたい -
from posts p, members m
↑↑↑どこから?→「posts」と「members」から。 -
where m.id=p.member_id
↑↑↑どこを対象に?→「membersのid」と「postsのmember_id」が一致しているカラムを。 -
order by id desc
↑↑↑どんな順番で?→desc(降順で)
while($stmt->fetch()):
?>
<div class="msg">
<?php if($picture): ?>
<img src="member_picture/<?php echo h($picture); ?>" width="48" height="48" alt=""/>
<?php endif; ?>
<p><?php echo h($message); ?><span class="name">(<?php echo h($name); ?>)</span></p>
<p class="day"><a href="view.php?id="><?php echo h($created); ?></a>
[<a href="delete.php?id=" style="color: #F33;">削除</a>]
</p>
</div>
<?php endwhile; ?>
- 名前や投稿内容、時間、写真などを対応させた形でecho記述する。
- while文で挟むことによって投稿件数の分だけ表示することができる
個別画面(view.php)を実装
- 各投稿の投稿時間のリンクを見ると、view.phpにリンクしている。
- 個別の投稿ページにリンクする仕様にしたいので、個別画面(view.php)を作っていく。
- view.phpはindex.php作ったのとそんなに変わらない
<?php
session_start();
require('library.php');
if(isset($_SESSION['id']) && isset($_SESSION['name'])){
$id = $_SESSION['id'];
$name = $_SESSION['name'];
}else{
header('Location: login.php');
exit();
}
$db = dbconnect();
?>
<!DOCTYPE html>
- 頭の方はそのまま移植してくる
<body>
<div id="wrap">
<div id="head">
<h1>ひとこと掲示板</h1>
</div>
<div id="content">
<?php $stmt = $db->prepare('select p.id, p.member_id, p.message, p.created, m.name, m.picture from posts p, members m where m.id=p.member_id order by id desc');
if(!$stmt){
die($db->error);
}
$success = $stmt->execute();
if(!$success){
die($db->error);
}
$stmt->bind_result($id, $member_id, $message, $created, $name, $picture);
while($stmt->fetch()):
?>
<div class="msg">
<?php if($picture): ?>
<img src="member_picture/<?php echo h($picture); ?>" width="48" height="48" alt=""/>
<?php endif; ?>
<p><?php echo h($message); ?><span class="name">(<?php echo h($name); ?>)</span></p>
<p class="day"><a href="view.php?id="><?php echo h($created); ?></a>
[<a href="delete.php?id=" style="color: #F33;">削除</a>]
</p>
</div>
<?php endwhile; ?>
<p>その投稿は削除されたか、URLが間違えています</p>
</div>
</div>
</body>
- 一覧用のループ部分を移植していったんそのまま中身に加える
- これを元に作成していく
- view.phpはURLにIDを指定していく必要がある。
- 指定されたIDが1件だけ表示されるようにしたい
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
if(!$id){
header('Locaion: index.php');
exit();
}
$db = dbconnect();
?>
<!DOCTYPE html>
-
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
↑↑↑filter_input→POST や GET などの変数をフィルタリングする
FILTER_SANITIZE_NUMBER_INT→数値しか受け取れないようにする処理
if(!$id){
header('Locaion: index.php');
exit();
}
- idが指定されていなければというindex.phpに戻す、という処理
<div class="msg">
<?php if($picture): ?>
<img src="member_picture/<?php echo h($picture); ?>" width="48" height="48" alt=""/>
<?php endif; ?>
<p><?php echo h($message); ?><span class="name">(<?php echo h($name); ?>)</span></p>
<p class="day"><a href="view.php?id=<?php echo h($id); ?>"><?php echo h($created); ?></a>
[<a href="delete.php?id=" style="color: #F33;">削除</a>]
</p>
</div>
<a href="view.php?id=">
- →
<a href="view.php?id=<?php echo h($id); ?>">
ここにidを指定
このidは少し上に移植してきたbind_resultで取得したid
↓↓↓
$stmt->bind_result($id, $member_id, $message, $created, $name, $picture);
while($stmt->fetch()):
- ちゃんとidが振られている
<div id="content">
<?php $stmt = $db->prepare('select p.id, p.member_id, p.message, p.created, m.name, m.picture from posts p, members m where m.id=p.member_id order by id desc');
if(!$stmt){
die($db->error);
}
$success = $stmt->execute();
if(!$success){
die($db->error);
}
- 移植してきたprepareのSQL文にwhereの条件を1つ追加する
$stmt = $db->prepare('select p.id, p.member_id, p.message, p.created, m.name, m.picture from posts p, members m where m.id=p.member_id order by id desc');
↓↓↓
$stmt = $db->prepare('select p.id, p.member_id, p.message, p.created, m.name, m.picture from posts p, members m where p.id=? and m.id=p.member_id order by id desc');
- whereのあとに
p.id=?
を追加(andも)
<div id="content">
<?php $stmt = $db->prepare('select p.id, p.member_id, p.message, p.created, m.name, m.picture from posts p, members m where p.id=? and m.id=p.member_id order by id desc');
if(!$stmt){
die($db->error);
}
$success = $stmt->execute();
if(!$success){
die($db->error);
}
- どのpostsのidを取得するかを指定している
<div id="content">
<?php $stmt = $db->prepare('select p.id, p.member_id, p.message, p.created, m.name, m.picture from posts p, members m where p.id=? and m.id=p.member_id order by id desc');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('i',$id);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
-
$stmt->bind_param('i',$id);
こちらを追記
これでidが指定されるので1件だけ取得されるはず
if($stmt->fetch()):
?>
<div class="msg">
<?php if($picture): ?>
<img src="member_picture/<?php echo h($picture); ?>" width="48" height="48" alt=""/>
<?php endif; ?>
<p><?php echo h($message); ?><span class="name">(<?php echo h($name); ?>)</span></p>
<p class="day"><a href="view.php?id="><?php echo h($created); ?></a>
[<a href="delete.php?id=" style="color: #F33;">削除</a>]
</p>
</div>
<?php else: ?>
<p>その投稿は削除されたか、URLが間違えています</p>
<?php endif ;?>
-
while($stmt->fetch()):
となっていたところを
if($stmt->fetch()):
に書き換える(1件だけでよいので)
<?php else: ?>
<p>その投稿は削除されたか、URLが間違えています</p>
<?php endif ;?>
- idが無いページの場合はエラーの文章のみ表示されるようにelseで設定
- ちゃんと個別のidページにリンクする
- idが無いページはエラー文だけ表示
削除機能を作る
- ここの「削除」のボタンの機能を作る
- ただ、自分以外のアカウントの投稿を削除できるようにしてはいけない
- 削除ボタンの出し分けを行う必要がある
<?php if($_SESSION['id'] === $member_id):
[<a href="delete.php?id=" style="color: #F33;">削除</a>]
<?php endif; ?>
各投稿のidと現在ログインしているidが一致していたら削除ボタンを表示
- これでログインしている人以外の削除ボタンが消える
削除ボタンをクリックした時の処理を追加
- delete.phpにジャンプする
- パラメータとしてidが指定できるようになっているので追記する
<?php if($_SESSION['id'] === $member_id): ?>
[<a href="delete.php?id=<?php echo h($id); ?>" style="color: #F33;">削除</a>]
<?php endif; ?>
- これで削除ボタンのリンクに各投稿のidが付与される
delete.phpを作成
<?php
session_start();
require('library.php');
if(isset($_SESSION['id']) && isset($_SESSION['name'])){
$id = $_SESSION['id'];
$name = $_SESSION['name'];
}else{
header('Location: login.php');
exit();
}
$db = dbconnect();
header('Location: index.php'); exit();
?>
- こちらも基本的にはindex.phpと同じなので上部のコード移植してくる
削除処理を追加
<?php
session_start();
require('library.php');
if(isset($_SESSION['id']) && isset($_SESSION['name'])){
$id = $_SESSION['id'];
$name = $_SESSION['name'];
}else{
header('Location: login.php');
exit();
}
$post_id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
if(!$post_id){
header('Location: index.php');
exit();
}
$db = dbconnect();
$stmt = $db->prepare('delete from posts where id=? limit 1');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('i', $post_id);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
header('Location: index.php'); exit();
?>
$db = dbconnect();
$stmt = $db->prepare('delete from posts where id=? limit 1');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('i', $post_id);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
- こちらに削除のSQL文を追加
$post_id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
if(!$post_id){
header('Location: index.php');
exit();
}
- idの取得と、postがうまくわたされていなかった場合にindex.phpに戻す処理も追加
-
$post_id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
↑↑↑filter_input→POST や GET などの変数をフィルタリングする
FILTER_SANITIZE_NUMBER_INT→数値しか受け取れないようにする処理
- 削除ボタンを押してみる
- 対象の投稿が一覧から削除される
事故を防ぐ処理を追加
- もしこのURLを直接アクセスすると対象投稿が消えてしまうことになる
- その対策を行う
- 実際にそのデータを削除してよいのかをチェックする
$db = dbconnect();
$stmt = $db->prepare('delete from posts where id=? and member_id=? limit 1');
if(!$stmt){
die($db->error);
}
$stmt->bind_param('ii', $post_id, $id);
$success = $stmt->execute();
if(!$success){
die($db->error);
}
-
$stmt = $db->prepare('delete from posts where id=? limit 1');
→$stmt = $db->prepare('delete from posts where id=? and member_id=? limit 1');
こちらに書き換え(and member_id=?)を追加 -
$stmt->bind_param('i', $post_id);
→$stmt->bind_param('ii', $post_id, $id);
こちらに書き換え('ii', $post_id, $id)を追加
もともとの状態
- select文を例にするとidが6のものを対象に表示させる
修正した状態
- member_idの条件も追加。member_idが異なると何も表示されない
※member_idが自分ではない状態
ログアウト機能の追加
- ログアウトボタンを押してログアウトする
- logout.phpにリンクされている
logout.php
<?php
session_start();
unset($_SESSION['id']);
unset($_SESSION['name']);
header('Location: login.php'); exit();
?>
- unset関数とは指定した変数や配列の要素などを削除するときに使用
- セッションで渡ってきたidとnameを削除している