Skip to content

PHP_13 「掲示板をPHPで作ろう」

Last updated on 2019/9/8

どんな掲示板?

ログインすることができ、投稿することができ、検索することができるシンプルな掲示板を作ってみましょう。

実現したい機能

  • ログイン機能
  • ログイン入力画面
  • 一覧を表示する機能
  • 書き込み画面
  • 書き込むする機能
  • ログインしてないと書き込めない機能
  • 検索機能

処理の流れ

ログイン機能

メールアドレスとパスワードでログインするシンプルなログイン機能を作ります。

まず、入力画面から入力したメールアドレスとパスワードを受け取ります。

// メールアドレスの取得
$email = "";
if (isset($_POST["email"])) {
    // もしemailが送られているなら変数の中に入れる
    $email = $_POST["email"];
} else {
    $message = "メールアドレスを入力してください。";
}
// パスワードの取得
$password = "";
if (isset($_POST["password"])) {
    // もしpasswordが送られているなら変数の中に入れる
    $password = $_POST["password"];
} else {
    $message = "パスワードを入力してください。";
}

そして、DBへの接続設定をして、メールアドレスとパスワードの組み合わせが一致するユーザーを探すSQL文を実行します。

// DBの設定をするオブジェクト
$pdo = new PDO(
    "mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock",
    "root", // ユーザー名
    "secret" // パスワード
);

// DBから特定の条件(メール, パスワード)
//   でデータがあるか調べる

$sql = "SELECT count(*) FROM user WHERE email=:email AND password=:password";

// WHERE ~ で条件を選択する
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':email', $email);
$stmt->bindParam(':password', $password);
$flag = $stmt->execute();

実行後、値が存在するならセッションにメールアドレスを保存します。そして、一覧画面に飛ばします。

if ($flag) {
    // emailとpasswordの組み合わせがあってた場合
    // セッションの処理
    // ログイン状態を維持する
    $_SESSION["email"] = $email;

    // 一覧ページに飛ばす処理
    header("Location: ./list.php");
    exit();
} else {
    // emailかpasswordが間違ってた場合
    $message = "メールアドレスかパスワードが間違っています。";
}

セッションにメールアドレスが存在する状態がログイン状態と判断し、メールアドレスでユーザーを識別できます。メールアドレスの保存だけでログイン状態とユーザー識別という2つの意味があるので、セッションに保存する値が最小にできます。

処理の最中に別のページに飛ばす処理はリダイレクトといい、PHPでは下記のように書きます。

header("Location: 飛ばしたい先のURL");

そして、必ずHTMLタグの出力よりも先に書きましょう。エラーが発生します。

exit();

上記を書くと、そこでPHPの処理を抜けれます。リダイレクトした後など書きましょう。

ログイン入力画面

ログイン入力画面では、ログイン状態なら入力画面を表示させずに一覧画面に遷移させます。

// ログインしてたら一覧ページに飛ばす
session_start();
if (isset($_SESSION["email"])) {
    // ログイン状態である
    // 一覧ページにジャンプさせる
    header("Location: ./list.php");
    // 処理をここで終わらせる
    exit();
}

セッションにメールアドレスが保存されているか判定します。

入力部品のnameは、ログイン機能のPOSTで送られる値のnameと合うように作ります。

    <form action="./login.php" method="post">
        メール: <input type="text" name="email">
        パスワード: <input type="password" name="password">
        <button type="submit">
            ログインする
        </button>
    </form>

一覧を表示する機能

投稿の一覧が取れるSQLを実行します。

// DBの設定をするオブジェクト
$pdo = new PDO(
    "mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock",
    "root", // ユーザー名
    "secret" // パスワード
);

// POSTテーブルから投稿一覧を表示
// SQLの実行
$stmt = $pdo->query('SELECT * FROM post');

取れたデータを1件1件、HTMLとともに出力します。

// データを読み込む
while ($row = $stmt->fetch()) {
    // 投稿ごとのHTMLの出力
    ?>
    <div class="box">
        <article class="media">
            <div class="media-left">
                <figure class="image is-64x64">
                    <img src="<?= $row["url"] ?>" alt="Image">
                </figure>
            </div>
            <div class="media-content">
                <div class="content">
                    <p>
                        <strong><?= $row["name"] ?></strong>
                        <br>
                        <?= $row["comment"] ?>
                    </p>
                </div>
            </div>
        </article>
    </div>
<?php
}

次に書き込み画面、書き込み処理を書いていきます。

書き込み画面

formタグを追加し、名前、画像のURL、メッセージを書き込むためのHTMLタグを設定します。

<form action="" method="post">
    <div class="field">
        <label class="label">Name</label>
        <div class="control">
            <input name="name" class="input" type="text" placeholder="Text input">
        </div>
    </div>
    <div class="field">
        <label class="label">image url</label>
        <div class="control">
            <input name="url" class="input" type="url" placeholder="Text input">
        </div>
    </div>

    <div class="field">
        <label class="label">Message</label>
        <div class="control">
            <textarea name="comment" class="textarea" placeholder="Textarea"></textarea>
        </div>
    </div>

    <div class="field is-grouped">
        <div class="control">
            <button type="submit" class="button is-link">送る</button>
        </div>
    </div>
</form>

項目の必要性によっては、required属性をつけましょう。

書き込み処理

まず、書き込み画面から送られてきた値を受け取ります。

$error = false;
$message = "書き込みしました<br>2秒後に一覧画面に遷移します。";

$name = ""; // 名前
$comment = ""; // 本文
$url = ""; // 画像のURL

if (isset($_POST["name"])) {
    $name = $_POST["name"];
} else {
    $name = "名無し";
}

if (isset($_POST["comment"])) {
    $name = $_POST["comment"];
} else {
    $error = true;
    $message = "コメントを入力してください。";
}

if (isset($_POST["url"])) {
    $url = $_POST["url"];
} else {
    // No Image画像の追加
    $url = "https://placehold.jp/150x150.png?text=No%20Image";
}

そして、受け取った値をDBに入れる処理を書きます。
値の有無を確認して、エラーになった際はDBの処理にはいらないような条件文も書きます。

if (!$error) {
    try {
        $pdo = new PDO(
            "mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock",
            "root", // ユーザー名
            "secret" // パスワード
        );
        $datetime = new DateTime();
        $date = $datetime->format('Y-m-d H:i:s');

        $stmt = $pdo->prepare(
            "INSERT INTO post
            (name, comment, date, url)
            VALUES ($name, $comment, $date, $url)"
        );
        $stmt->execute(
            [
                ':name' => $name,
                ':comment' => $comment,
                ':date' => $date,
                ':url' => $url,
            ]
        );

    } catch (PDOException $e) {
        $error = true;
        $message = "データベースのエラーが発生しました。" . $e->getMessage();
    }
}

全体のコード

ログイン機能 (login.php)

<?php
// ==============================
// ログイン処理をするPHPファイル
// ==============================

// セッションの処理を行う場合に宣言する。
session_start();

// エラーメッセージの定義
$message = "エラーが発生しました。";

// メールアドレスの取得
$email = "";
if (isset($_POST["email"])) {
    // もしemailが送られているなら変数の中に入れる
    $email = $_POST["email"];
} else {
    $message = "メールアドレスを入力してください。";
}
// パスワードの取得
$password = "";
if (isset($_POST["password"])) {
    // もしpasswordが送られているなら変数の中に入れる
    $password = $_POST["password"];
} else {
    $message = "パスワードを入力してください。";
}

// emailと
// passwordが DBに入っている値と
// あっているかどうか?

if (empty($password) || empty($email)) {
    // どちらかが空の場合
    // セッションで
    //      メールアドレスの値が残っている場合は
    //      ログインしているとみなす。
    if (isset($_SESSION["email"])) {
        // 一覧ページに飛ばす処理
        header("Location: ./list.php");
        exit();
    }
} else {
    // どちらも空じゃない
    // DBの設定をするオブジェクト
    $pdo = new PDO(
        "mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock",
        "root", // ユーザー名
        "secret" // パスワード
    );

    // DBから特定の条件(メール, パスワード)
    //   でデータがあるか調べる
    // $pdo->prepare(SQLの文);
    // Insert, Select
    $sql = "SELECT count(*) FROM user WHERE
        email=:email AND password=:password";
    // WHERE ~ で条件を選択する
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':email', $email);
    $stmt->bindParam(':password', $password);
    $flag = $stmt->execute();

    if ($flag) {
        // emailとpasswordの組み合わせがあってた場合
        // セッションの処理
        // ログイン状態を維持する
        $_SESSION["email"] = $email;

        // 一覧ページに飛ばす処理
        header("Location: ./list.php");
        exit;
    } else {
        // emailかpasswordが間違ってた場合
        $message = "メールアドレスかパスワードが間違っています。";
    }
}

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>メッセージ</title>
</head>

<body>
    <h1>エラー</h1>
    <p><?= $message ?></p>
</body>

</html>

ログイン画面(login-input.php)

<?php

// ==============================
// ログイン画面を表示するPHPファイル
// ==============================

// ログインしてたら一覧ページに飛ばす
session_start();
if (isset($_SESSION["email"])) {
    // ログイン状態である
    // 一覧ページにジャンプさせる
    header("Location: ./list.php");
    // 処理をここで終わらせる
    exit();
}
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>

<body>
    <form action="./login.php" method="post">
        メール: <input type="text" name="email">
        パスワード: <input type="password" name="password">
        <button type="submit">
            ログインする
        </button>
    </form>
</body>

</html>

一覧表示画面(list.php)

<?php

// ==============================
// 一覧表示処理をするPHPファイル
// ==============================
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>

</head>

<body>
    <section class="section">
        <div class="container">
            <?php
            // データベースに接続

            // DBの設定をするオブジェクト
            $pdo = new PDO(
                "mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock",
                "root", // ユーザー名
                "secret" // パスワード
            );

            // POSTテーブルから投稿一覧を表示
            // SQLの実行
            $stmt = $pdo->query(
                'SELECT * FROM post'
            );

            // データを読み込む
            while ($row = $stmt->fetch()) {
                // 投稿ごとのHTMLの出力
                ?>
                <div class="box">
                    <article class="media">
                        <div class="media-left">
                            <figure class="image is-64x64">
                                <img src="<?= $row["url"] ?>" alt="Image">
                            </figure>
                        </div>
                        <div class="media-content">
                            <div class="content">
                                <p>
                                    <strong><?= $row["name"] ?></strong> <small><?= $line[2] ?></small>
                                    <br>
                                    <?= $row["comment"] ?>
                                </p>
                            </div>
                        </div>
                    </article>
                </div>
            <?php
        }

        ?>
        </div>
    </section>
</body>

</html>

書き込み画面(post-input.php)

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>入力画面 | 掲示板</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>

</head>

<body>
    <section class="section">
        <div class="container">
            <h1 class="title">
                入力フォーム
            </h1>
            <p class="subtitle">
                名前かコメントは必須です。書き込み一覧は<a href="./0527-read.php">こちら</a>
            </p>
            <form action="write.php" method="post">
                <div class="field">
                    <label class="label">Name</label>
                    <div class="control">
                        <input name="name" class="input" type="text" placeholder="Text input">
                    </div>
                </div>
                <div class="field">
                    <label class="label">画像url</label>
                    <div class="control">
                        <input name="url" class="input" type="url" placeholder="URL">
                    </div>
                </div>

                <div class="field">
                    <label class="label">Message</label>
                    <div class="control">
                        <textarea name="comment" class="textarea" placeholder="テキストを打ってね"></textarea>
                    </div>
                </div>

                <div class="field is-grouped">
                    <div class="control">
                        <button type="submit" class="button is-link">送る</button>
                    </div>
                </div>
            </form>
        </div>
    </section>

</body>

</html>

書き込み機能(write.php)

<?php
// ==============================
// 書き込み処理をするPHPファイル
// ==============================

// ログインの確認をする
session_start();
if (!isset($_SESSION["email"])) {
    header("Location: ./login-input.php");
    exit;
}

$error = false; // エラーフラグ
$message = "書き込みしました<br>2秒後に一覧画面に遷移します。";

$name = ""; // 名前
$comment = ""; // 本文
$url = ""; // 画像のURL

// 送られてきた値の検査をする
if (isset($_POST["name"])) { // nameの検査。
    $name = $_POST["name"];
} else {
    $name = "名無し"; //もし送られていなかったら名無しにする
}

if (isset($_POST["comment"])) { // コメントの検査
    $comment = $_POST["comment"];
} else {
    $error = true; // もしコメントがないならエラーにする
    $message = "コメントを入力してください。";
}

if (
    isset($_POST["url"]) && !empty($_POST["url"])
) { // URLの検査
    $url = $_POST["url"];
} else {
    // No Image画像の追加 (一行で書いてね ↓↓↓↓↓↓↓↓↓↓↓↓↓↓)
    $url = "https://placehold.jp/150x150.png?text=No%20Image";
}

if (!$error) {
    try {
        $pdo = new PDO(
            "mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock",
            "root", // ユーザー名
            "secret" // パスワード
        );
        $datetime = new DateTime();
        $date = $datetime->format(
            'Y-m-d H:i:s'
        );

        $stmt = $pdo->prepare("INSERT INTO post (name, comment, date, url) VALUES (?, ?, ?, ?)");
        $stmt->execute([$name, $comment, $date, $url]);
    } catch (PDOException $e) {
        $error = true;
        $message = "データベースのエラー" . $e->getMessage();
    }
}
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>書き込み | 掲示板</title>
    <?php if (!$error) { ?>
        <meta http-equiv="refresh" content=" 2; url=./list.php">
    <?php } ?>

</head>

<body>
    <?= $message ?>
</body>

</html>

検索入力画面(search-input.php)

<?php
// ==============================
// 検索入力画面のPHPファイル
// ==============================
$page_title = "検索画面 | 掲示板システム";
include "./header.php";
?>
<h1>検索画面</h1>
<p>
    検索すると投稿が表示されます。
</p>
<form action="./search.php" method="post">
    <input type="text" name="keyword">
    <button type="submit">
        検索する
    </button>
</form>
<?php
include "./footer.php";
?>

検索機能(search.php)

<?php
// ==============================
// 検索結果を表示するPHPファイル
// ==============================

$error = false;
$message = "";
$keyword = "";

if (isset($_POST["keyword"]) && !empty($_POST["keyword"])) {
    // 検索ワードの検査
    $keyword = $_POST["keyword"];
} else {
    $error = true;
    $message = "検索ワードを入力してください。";
}

if (!$error) { // エラーじゃない場合
    try {
        // データベースに接続
        // DBの設定をするオブジェクト
        $pdo = new PDO("mysql:dbname=bbs;host=db;charaset=utf8;unix_socket=/tmp/mysql.sock", "root", "secret");

        // POSTテーブルから検索した投稿を表示
        // SQLの実行
        $stmt = $pdo->prepare(
            "SELECT * FROM post WHERE name LIKE ? OR comment LIKE ?"
        );
        $stmt->execute(
            ["%$keyword%", "%$keyword%"]
        );
    } catch (PDOException $e) {
        $error = true;
        $message = "データベースのエラー" . $e->getMessage();
    }
}


if ($error) {
    // エラー画面を表示
    ?>
    <h2>エラーが発生しました。</h2>
    <p><?= $message ?></p>
<?php
} else {
    ?>
    <h2>検索結果</h2>
    <p>
        <strong>
            <?= $keyword ?>
        </strong>
        の検索結果一覧</p>
    <?php

    // データを読み込む
    while ($row = $stmt->fetch()) {
        // 投稿ごとのHTMLの出力
        ?>
        <div class="box">
            <article class="media">
                <div class="media-left">
                    <figure class="image is-64x64">
                        <img src="<?= $row["url"] ?>" alt="Image">
                    </figure>
                </div>
                <div class="media-content">
                    <div class="content">
                        <p>
                            <strong><?= $row["name"] ?></strong>
                            <br>
                            <?= $row["comment"] ?>
                        </p>
                    </div>
                </div>
            </article>
        </div>
    <?php
}
}

ヘッダー(header.php)

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><?= $page_title ?></title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>

</head>

<body>

    <nav class="navbar is-transparent">
        <div class="navbar-brand">
            <a class="navbar-item" href="https://bulma.io">
                <img src="https://bulma.io/images/bulma-logo.png" alt="Bulma: a modern CSS framework based on Flexbox" width="112" height="28">
            </a>
            <div class="navbar-burger burger" data-target="navbarExampleTransparentExample">
                <span></span>
                <span></span>
                <span></span>
            </div>
        </div>

        <div id="navbarExampleTransparentExample" class="navbar-menu">
            <div class="navbar-start">
                <a class="navbar-item" href="https://bulma.io/">
                    Home
                </a>
                <div class="navbar-item has-dropdown is-hoverable">
                    <a class="navbar-link" href="https://bulma.io/documentation/overview/start/">
                        Docs
                    </a>
                    <div class="navbar-dropdown is-boxed">
                        <a class="navbar-item" href="https://bulma.io/documentation/overview/start/">
                            Overview
                        </a>
                        <a class="navbar-item" href="https://bulma.io/documentation/modifiers/syntax/">
                            Modifiers
                        </a>
                        <a class="navbar-item" href="https://bulma.io/documentation/columns/basics/">
                            Columns
                        </a>
                        <a class="navbar-item" href="https://bulma.io/documentation/layout/container/">
                            Layout
                        </a>
                        <a class="navbar-item" href="https://bulma.io/documentation/form/general/">
                            Form
                        </a>
                        <hr class="navbar-divider">
                        <a class="navbar-item" href="https://bulma.io/documentation/elements/box/">
                            Elements
                        </a>
                        <a class="navbar-item is-active" href="https://bulma.io/documentation/components/breadcrumb/">
                            Components
                        </a>
                    </div>
                </div>
            </div>

            <div class="navbar-end">
                <div class="navbar-item">
                    <div class="field is-grouped">
                        <p class="control">
                            <a class="bd-tw-button button" data-social-network="Twitter" data-social-action="tweet" data-social-target="http://localhost:4000" target="_blank" href="https://twitter.com/intent/tweet?text=Bulma: a modern CSS framework based on Flexbox&hashtags=bulmaio&url=http://localhost:4000&via=jgthms">
                                <span class="icon">
                                    <i class="fab fa-twitter"></i>
                                </span>
                                <span>
                                    Tweet
                                </span>
                            </a>
                        </p>
                        <p class="control">
                            <a class="button is-primary" href="https://github.com/jgthms/bulma/releases/download/0.7.5/bulma-0.7.5.zip">
                                <span class="icon">
                                    <i class="fas fa-download"></i>
                                </span>
                                <span>Download</span>
                            </a>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </nav>

フッター(footer.php)

<footer>
    Copyright 2019 hirosawa
</footer>
</body>

</html>

ログアウト機能 (logout.php)

<?php
// ==============================
// ログアウトするPHPファイル
// ==============================
session_start();

unset($_SESSION["email"]);

// ログインページに飛ばす処理
header("Location: ./login-input.php");
exit;