This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

ひとこと掲示板

Last updated at Posted at 2024-03-09

ひとこと掲示板のようなものを作りたい

240309_02.png
240309_03.png

準備したデータベース(min_bbs_02で作成)

▼members

image.png

▼posts

240309_04.png

項目を入力しなければエラーが起こる処理を作成

240309_05.png
※この画面に関してはすでにサンプルで作成されている

240309_06.png

  • 作りたい動き
    • 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配列に格納していたということ

ここまででデータがうまく移動できているかチェック

240317_02.png

  • ニックネームなどを適当に入力して、「入力内容を確認する」を押す

240317_01.png

  • check.phpに飛ぶ。保存した配列もvar_dumpでちゃんと表示されている!

画像のアップロード処理について

  • ここまではあくまで画像の内容チェック処理のみだけで、アップロード処理は無い状態
if(empty($error)){
  $_SESSION['form'] = $form;

  // 画像のアップロード
  $filename = date('YmdHis') . '_' . $image['name'];
  var_dump($filename);
  exit();

  header('Location: check.php');
  exit();
}
  • アップロード処理を追加してみて、入力完了を押してみる

240317_03.png

  • こんな感じでファイル名に日付が付いた状態になっている
  • 同じファイル名になっても重複しないようにしている
  • 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」を作った

  • これまでの手順通り画像をアップしてみるとフォルダにアップした画像が格納されるようになる
    240317_04.png

ファイルの権限エラーの対策を加えておく

// 画像のアップロード
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']を空で設定

240317_05.png

  • これで画像をまたアップしてみると、画像ファイルの名前が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を調べてください、という処理になっている

240317_06.png

  • pngファイルであることがわかる
<?php
$finfo = new finfo();

$mime = $finfo->file('index.php', FILEINFO_MIME_TYPE);
echo $mime;
  • 対象のファイルをindex.phpに変えてみる
    240317_07.png
  • ちゃんとファイル形式がphpで表示されている

確認画面の作成

<?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>
  • ニックネームの欄にecho文を追加
  • 受け取ってきた['form']の値を記入する
    240317_08.png
  • 入力して確認画面に進むと入力したテキスト「a」がちゃんと表示されている
<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">&laquo;&nbsp;書き直す</a> | <input type="submit" value="登録する" /></div>
			</form>

240317_09.png

  • メールアドレス、画像に関しても同様に追加すると確認画面に表示される

いきなり確認画面(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');
}
?>
  • 過去回に合わせてSQL文を追加し、データベースに接続する。
  • ここまで入力した上で、入力欄を埋めて登録するとデータベースにも内容が入る
    (なんか入らなかったけど)
    240317_10.png

パスワードの登録を暗号化する必要あり

240317_11.png

  • 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に移動させる

入力内容のやり直しができるように

240317_12.png

  • 確認画面の「書き直す」を押すと入力画面に値を残したまま戻れるようにしたい
<div><a href="index.php?action=rewrite">&laquo;&nbsp;書き直す</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'])){
    ここの条件です

メールアドレスの重複チェックを修正していく

240317_13.png

まずはデータベースへの接続を共通ファイルに移行していく

<?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();

ログイン画面を作る

240317_14.png

  • login.phpがあらかじめ準備されている
  • こんな画面なので、昨日を追加していく
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST'){
    echo 'submit';
}
?>
  • login.phpの頭にこちら追加してみる
  • 「ログインする」ボタン押してみるとそれだけで「sumit」echoで表示される
  • if($_SERVER['REQUEST_METHOD'] === 'POST'){→POSTが実行されたら、、という意味

240317_15.png

<?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);で表示してみる

240317_16.png
240317_17.png

  • メール欄に適当に入力してボタン押すと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>&raquo;<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);
    }
}
?>

240317_19.png

  • ログイン画面で入力した値がデータベースに登録したログイン情報と一致しているかを確認している
  • ログインチェックSQL文のlimit 1はセキュリティの意味合いで入れている
  • $stmt->bind_param('s', $email);
    →なぜpasswordも含まずemailだけなのか。
    →入力欄で受け取っているパスワードと、データベースに記録されているパスワード(ハッシュ化されたもの)が異なるので、ここはemailのみでよい
    まずはメールアドレスだけ受け取って、passwordが合っているかを確認する、という手順を踏んでいる
  • $stmt->bind_result($id, $name, $hash);
    $hashここはなぜ$passwordじゃないのか。
    → 同じ変数が上部の入力側passwordの変数として使われているため
  • 一致していたらvar_dump($hash);でハッシュ化されたパスワードを表示してみている

240317_20.png

  • 表示される

ハッシュ化されたパスワードと、ユーザーが入力したパスワードが合っているかを確認する

<?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を表示させてみる

240317_21.png

  • ログイン登録の際に入力した名前「テストくん」が表示されていることを確認
もしログインしていない状態ならページ自体表示できないような処理を追加したい
<?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には入れないように設定できている

一覧画面(メイン画面)を作る

240317_22.png

  • こちらの画面から投稿すると、一覧画面にそれぞれの投稿が表示されるような画面を作る
// メッセージの投稿
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→文字列しか受け取れないようにする処理

  • messageとidの内容ををデータベースに登録する処理になっている
    240317_25.png

  • ↑↑↑【大注意!!】不要なカラムを最初に追加しているせいでうまく処理が通らなかった

  • 「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
240317_23.png

  • データベースに投稿内容が入る
  • 「member_id」の「2」はmembersテーブルのidと対応している
    240317_27.png
  • テストくん(id→2)が投稿している状態
ページ再読み込みすると再度投稿が送信されてしまうので修正する

240317_28.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文で挟むことによって投稿件数の分だけ表示することができる

240317_29.png

個別画面(view.php)を実装

240317_30.png
240317_31.png

  • 各投稿の投稿時間のリンクを見ると、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>
  • 一覧用のループ部分を移植していったんそのまま中身に加える
  • これを元に作成していく

240317_32.png

  • 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()):

240317_33.png
240317_34.png

  • ちゃんと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で設定

240317_36.png
240317_37.png

  • ちゃんと個別のidページにリンクする

240317_38.png

  • idが無いページはエラー文だけ表示

削除機能を作る

240317_39.png

  • ここの「削除」のボタンの機能を作る
  • ただ、自分以外のアカウントの投稿を削除できるようにしてはいけない
  • 削除ボタンの出し分けを行う必要がある
<?php if($_SESSION['id'] === $member_id):    
  [<a href="delete.php?id=" style="color: #F33;">削除</a>]
<?php endif; ?>

各投稿のidと現在ログインしているidが一致していたら削除ボタンを表示
240317_40.png

  • これでログインしている人以外の削除ボタンが消える
削除ボタンをクリックした時の処理を追加
  • delete.phpにジャンプする
  • パラメータとしてidが指定できるようになっているので追記する
<?php if($_SESSION['id'] === $member_id): ?>   
  [<a href="delete.php?id=<?php echo h($id); ?>" style="color: #F33;">削除</a>]
<?php endif; ?>

240317_41.png

  • これで削除ボタンのリンクに各投稿の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→数値しか受け取れないようにする処理

240317_42.png
240317_43.png

  • 削除ボタンを押してみる
  • 対象の投稿が一覧から削除される
事故を防ぐ処理を追加

240317_44.png

  • もしこの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)を追加

もともとの状態

240317_45.png
240317_46.png

  • select文を例にするとidが6のものを対象に表示させる
修正した状態

240317_47.png
240317_48.png

  • member_idの条件も追加。member_idが異なると何も表示されない
    ※member_idが自分ではない状態

ログアウト機能の追加

240317_49.png

  • ログアウトボタンを押してログアウトする
  • logout.phpにリンクされている
logout.php
<?php
session_start();

unset($_SESSION['id']);
unset($_SESSION['name']);

header('Location: login.php'); exit();
?>
  • unset関数とは指定した変数や配列の要素などを削除するときに使用
  • セッションで渡ってきたidとnameを削除している
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up