確認画面

送信されたPOSTデータから、確認画面を表示します。

ファイル構成

php_sns/
├── lib/
│        └── Model.php
├── regist/
│        ├── add.php
│        ├── complete.php
│        ├── confirm.php
│        ├── index.php
│        └── input.php
├── app.php
└── env.php

確認画面作成

HTML基本タグ作成

POSTデータを取得処理の下に、HTMLの基本タグを追加します。

confirm.php
<?php
require_once "../app.php";

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    exit;
}

$regist = $_POST;
?>

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= SITE_TITLE ?></title>
    <base href="<?= BASE_URL ?>">
    <script src="https://cdn.tailwindcss.com"></script>
</head>

<main id="register" class="flex justify-center">
    <div class="w-1/2 mt-3 p-5">
        <h2 class="text-2xl mb-3 font-normal text-center">Sign Up</h2>
    </div>
</main>

</html>

HTML表示確認

入力画面からPOST送信して、確認画面が表示されるか確認します。

データ表示

取得した「アカウント名」「Email」「名前」をHTML表示します。

regist/confirm.php
...

<main id="register" class="flex justify-center">
    <div class="w-1/2 mt-3 p-5">
        <h2 class="text-2xl mb-3 font-normal text-center">Sign Up</h2>
        <div class="bg-white border rounded px-8 pt-6 pb-8 mb-4">
            <div class="mb-4">
                <p class="text-center text-gray-700 text-md">この内容で登録しますか?</p>
            </div>
            <div class="mb-4">
                <label class="block text-gray-700 text-md font-bold mb-2" 
                       for="account_name">
                    アカウント名
                </label>
                <p class="appearance-none w-full py-2 px-0 text-gray-700" id="account_name">
                    <?= $regist['account_name'] ?>
                </p>
            </div>
            <div class="mb-6">
                <label class="block text-gray-700 text-md font-bold mb-2" for="email">
                    Email
                </label>
                <p class="appearance-none w-full py-2 px-0 text-gray-700" id="email">
                    <?= $regist['email'] ?>
                </p>
            </div>
            <div class="mb-6">
                <label class="block text-gray-700 text-md font-bold mb-2" for="name">
                    名前
                </label>
                <p class="appearance-none w-full py-2 px-0 text-gray-700" id="name">
                    <?= $regist['name'] ?>
                </p>
            </div>

        </div>
    </div>
</main>
...

戻るリンク

入力画面「regist/input.php」に戻るリンクを追加します。

regist/confirm.php
           <div class="flex">
                <a href="regist/input.php" class="w-full
                    mx-1 mb-4 py-2 px-4
                    text-gray-500 
                    text-center
                    bg-gray-100
                    hover:bg-gray-200 
                    rounded">
                    修正
                </a>
            </div>

リンク確認

【修正】リンクで入力画面に戻ります。

入力画面では、前回の入力データは保持されていません。

セッション

入力したデータを入力画面やデータ登録処理で利用できるように、セッションに保存します。

セッション登録処理

セッション開始処理

「app.php」が実行されたら、セッション開始するようにします。

app.php
<?php
require_once "env.php";

// セッション開始
session_start();
session_regenerate_id(true);

const BASE_DIR = __DIR__;
const APP_DIR = __DIR__ . "/app/";
const LIB_DIR = __DIR__ . "/lib/";

require_once LIB_DIR . 'Model.php';

セッション登録

POSTデータをセッションに登録します。$_SESSIONのキーは「env.php」で設定した APP_KEY と「regist」にします。

regist/confirm.php
<?php
require_once "../app.php";

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    exit;
}

// POSTデータをセッション登録
$_SESSION[APP_KEY]['regist'] = $_POST;

$regist = $_POST;
?>

セッション取得

入力画面でセッションデータを取得してデバッグ確認します。

regist/input.php
<?php
require_once "../app.php";

$regist = $_SESSION[APP_KEY]['regist'];
// デバッグ確認
var_dump($regist);
?>
...

セッションデータ確認

確認画面でセッション登録したら、入力画面に戻ってセッションが保存されているか確認します。これで、どの画面でもセッションデータにアクセスできます。

セッション表示

入力画面のフォームに、セッションに登録した前回入力したデータを表示します。

regist/input.php
...
<main id="regist" class="flex justify-center">
    <div class="w-1/2 mt-3 p-5">
        <h2 class="text-2xl mb-3 font-normal text-center">Sign Up</h2>
        <form action="regist/confirm.php" method="post">
            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" type="text" 
                        name="account_name" id="account_name" 
                        value="<?= @$regist['account_name'] ?>" placeholder=" " required>
                <label for="account_name" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    アカウント名
                </label>
                <p class="text-sm text-red-600"><?= @$errors['email'] ?></p>
            </div>
            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" type="email" 
                        name="email" id="email" value="<?= @$regist['email'] ?>" 
                        placeholder=" " required>
                <label for="email" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    Email
                </label>
                <p class="text-sm text-red-600"><?= @$errors['email'] ?></p>
            </div>
            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3 
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" 
                        type="password"  name="password"  id="password"  value="" 
                        placeholder=" " required>
                <label for="password" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    パスワード
                </label>
                <div class="text-sm text-red-600"><?= @$errors['password'] ?></div>
            </div>

            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" type="text" 
                        name="name" id="name" value="<?= @$regist['name'] ?>" 
                        placeholder=" " required>
                <label for="name" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    名前
                </label>
                <p class="text-sm text-red-600"><?= @$errors['name'] ?></p>
            </div>

            <div>
                <button id="submit_button" class="w-full
                        mb-2 py-2 px-4 bg-sky-500 
                        hover:bg-sky-700 
                        text-white 
                        rounded-lg
                        disabled:bg-blue-300" disabled>
                    次へ
                </button>
            </div>

            <div class="text-center">
                <ul>
                    <li class="pt-3">
                        <a href="login/" class="text-blue-700 hover:text-blue-600">Sign in はこちら</a>
                    </li>
                    <li class="pt-3">
                        <a href="forget_password/" class="text-blue-700 hover:text-blue-600">
                        パスワードを忘れた方
                        </a>
                    </li>
                </ul>
            </div>
        </form>
    </div>
</main>
...

ユーザ登録処理

登録フォーム追加

確認画面に「regist/add.php」へのフォームリクエスト(POST)を追加します。

regist/confirm.php
...
            <!-- 「regist/add.php」へのフォームリクエスト追加 -->
            <form action="regist/add.php" method="post">
                <div class="flex">
                    <!-- 登録ボタン追加 -->
                    <button id="submit_button" class="w-full
                        mx-1 mb-4 py-2 px-4
                        text-white 
                        bg-sky-500 
                        hover:bg-sky-700 
                        rounded-lg">
                        登録
                    </button>
                    <a href="regist/input.php" class="w-full
                        mx-1 mb-4 py-2 px-4
                        text-gray-500 
                        text-center
                        bg-gray-100
                        hover:bg-gray-200 
                        rounded">
                        修正
                    </a>
                </div>
            </form>
...

セッション取得

regist/add.php
<?php 
// app.php を読み込み
require_once '../app.php';

// POSTリクエストでなければ何も表示しない
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

// セッションデータ取得
$regist = $_SESSION[APP_KEY]['regist'];

パスワードハッシュ

ハッシュデータはデータを復号(元に戻す)できないので、パスワードを保存するのに有効です。

password_hash()

password_hash() は、パスワードハッシュするためのメソッドです。

password_hash(パスワード, オプション);

パスワードがハッシュ化されると解読不可能な文字列に変換されます。

$2y$10$S9n.vg7s8T4lRIOiggVlMOuWgR1tZSBKLwToX5S.nbZ2iRcZvK8w6

password_verify()

password_verify() で生のパスワードと、ハッシュが一致しているかの検証(True/False)できます。

bool = password_verify(文字列, ハッシュ)
regist/add.php
<?php 
require_once '../app.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

$regist = $_SESSION[APP_KEY]['regist'];

// パスワードのハッシュ化
$regist['password'] = password_hash($regist['password'], PASSWORD_DEFAULT);

ユーザデータ登録

usersテーブルにデータ登録するSQLを作成し、実行します。データ登録後は「complete.php」にリダイレクトします。

regist/add.php
<?php 
require_once '../app.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

$regist = $_SESSION[APP_KEY]['regist'];

$regist['password'] = password_hash($regist['password'], PASSWORD_DEFAULT);

// データベースに接続
$model = new Model();

// users テーブルにレコードを挿入するSQL
$sql = "INSERT INTO users (account_name, name, email, password)
        VALUES (:account_name, :name, :email, :password);
        ";

// データベースに登録
$stmt = $model->pdo->prepare($sql);
$stmt->execute($regist);

// 「complete.php」にリダイレクト
header('Location: complete.php');

完了画面作成

「regist/complete.php」を作成し、完了画面を表示します。 このとき、ユーザのセッションは削除しておきます。

regist/complete.php
<?php
require_once "../app.php";

if (isset($_SESSION[APP_KEY]['regist'])) {
    unset($_SESSION[APP_KEY]['regist']);
}
?>

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= SITE_TITLE ?></title>
    <base href="<?= BASE_URL ?>">
    <script src="https://cdn.tailwindcss.com"></script>
</head>

<body>
    <main id="register" class="flex justify-center">
        <div class="w-1/2 mt-3 p-5">
            <h2 class="text-2xl mb-3 font-normal text-center">ユーザ登録完了</h2>
            <div class="mb-4">
                <p class="text-center text-gray-700 text-md">
                ユーザ登録が完了しました。
                </p>
            </div>
            <div class="mb-4 flex justify-center">
                <a href="login/" class="w-full
                        mx-1 mb-4 py-2 px-4
                        text-gray-500 
                        text-center
                        bg-gray-100
                        hover:bg-gray-200 
                        rounded">
                    ログイン
                </a>
                <a href="" class="w-full
                        mx-1 mb-4 py-2 px-4
                        text-gray-500 
                        text-center
                        bg-gray-100
                        hover:bg-gray-200 
                        rounded">
                    トップ
                </a>
            </div>
        </div>
    </main>
</body>

</html>

データ確認

確認画面から【登録】ボタンをクリックし、usersテーブルにデータ登録されるか確認してみましょう。

SQLインジェクション

SQLインジェクションとは

フォームから悪意のあるデータ送信して、SQLを書き換えて実行できる可能性があります。これをSQLインジェクション攻撃といいます。

query() の危険性

送信データをそのまま query() で実行すると、SQLインジェクションの対象となります。 ログインフォームの Email欄に以下の文字を入力してログインしてみましょう。

' or 1 = '1

SQLインジェクションの結果

正しい Email を入力しなくてもユーザデータを取得できました。

array(7) {
  ["id"]=>
  int(1)
  ["name"]=>
  string(13) "三宅 直子"
  ["email"]=>
  string(19) "[email protected]"
  ["password"]=>
...
}

実行されたSQL

SQLの条件は「Email を空欄」または「すべて」となり、すべてのユーザを取得するSQLに変換されました。

SELECT * FROM users WHERE email = '' or 1 = '1'

サニタイズ

サニタイズとは

フォーム送信で悪意のあるデータを変換して無力化することを、サニタイズ(Sanitize) といいます。

htmlspecialchars() でサニタイズ

HTMLやURLなどはSQLインジェクションの文字列を含んでいるため、htmlspecialchars() で、特殊文字をHTMLエンティティに変換します。

htmlspecialchars(値, ENT_QUOTES, "UTF-8")

例えば '(シングルクォーテーション)&#039; に変換されるので、SQLインジェクション対策に有効です。

変換前
' or 1 = '1
変換後
&#039; or 1 = &#039;1

POSTデータのサニタイズ

check() を追加して、サニタイズ処理の実装します。

regist/confirm.php
<?php
require_once "../app.php";

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    exit;
}

$_SESSION[APP_KEY]['regist'] = $_POST;
// サニタイズ
$regist = sanitize($_POST);

function sanitize($posts)
{
    foreach ($posts as $column => $post) {
        $posts[$column] = htmlspecialchars($post, ENT_QUOTES, 'UTF-8');
    }
    return $posts;
}
?>

$_POST をサニタイズします。

PHP + MySQL Webサーバプログラミング