MySQLを利用した掲示板 プログラム演習9

それではMySQLを利用して簡単な掲示板を作成してみましょう。掲示板の仕様は以下の通りにします

【仕様】

  • 掲示板からは名前とコメントを投稿できる
  • メイン画面には投稿フォームを設置する
  • メイン画面には過去投稿が新しい順に表示できるようにする
  • 名前とコメントは両方必須にして、空欄の場合はエラーと画面に表示する
  • XSS対策はちゃんとする(ウエブセキュリティ参照)

DBの設計

今回、掲示板情報を保存するDBは前回作成したprog9というDBでTableは以下のbbsというテーブルを一つ作ります

bash-3.2$ cat create_table.sql 
CREATE TABLE bbs (
    id          int auto_increment,
    name        varchar(255) not null default '',
    body        text not null default '',
    pubdate     timestamp,
    primary key(id)
) DEFAULT CHARSET=utf8;

Tableを作成します

bash-3.2$ /Applications/MAMP/Library/bin/mysql -u root -p prog9 < create_table.sql 
mysql> show fields from bbs;
+---------+--------------+------+-----+-------------------+----------------+
| Field   | Type         | Null | Key | Default           | Extra          |
+---------+--------------+------+-----+-------------------+----------------+
| id      | int(11)      | NO   | PRI | NULL              | auto_increment | 
| name    | varchar(255) | NO   |     |                   |                | 
| body    | text         | NO   |     |                   |                | 
| pubdate | timestamp    | NO   |     | CURRENT_TIMESTAMP |                | 
+---------+--------------+------+-----+-------------------+----------------+
4 rows in set (0.01 sec)

idカラムは自動採番(auto_increment)されるカラムでpubdateは更新(投稿)日付が自動で入るようにしてあります。掲示板に投稿する場合はこのbbsテーブルに name=名前 body=本文で1行インサートします。過去投稿をリストする場合は

SELECT * FROM bbs ORDER BY id DESC;

で取得します。

サイトの開発

基本的には演習6のプログラムを基本として開発します.

|-- ACTION
|   |-- Action_Base.class.inc
|   |-- Action_main.class.inc
|   `-- Action_post.class.inc
|-- COMMON
|   |-- Config.inc
|   |-- Logs.class.inc
|   `-- RequestManager.class.inc
|-- MODEL
|   `-- bbsDataAccessMySQL.class.inc
|-- VIEW
|   |-- ViewManager.class.inc
|   |-- cache
|   |-- footer.tpl
|   |-- form.tpl
|   |-- header.tpl
|   |-- list.tpl
|   `-- main.tpl
|-- create_table.sql
`-- index.php

action=main(default)でメイン画面を表示します。action=postで投稿を行います. Action_post.class.incで投稿を行いますが、投稿完了時に即action=mainにリダイレクトさせます。以下がAction_post.class.incのececute関数です

function execute() {
    Logs::debug("EXECUTE");
    $dataControl = new bbsDataAccessMySQL();
    if(($dataControl->saveBBS($this->req->getAll())) == bbsDataAccessMySQL::REQUEST_OK) {
        header("Location: ./" . $this->templParams['env']['scriptname']);
    } else {
        header("Location: ./" . $this->templParams['env']['scriptname'] . "?error=1");
    }
    exit;
}

投稿完了後に header("Location")でメイン画面に遷移させます。投稿がエラーの場合はerror=1を付けて遷移させます。MySQLを操作する(投稿をINSERTる。過去投稿をSELECTする)は MODEL/bbsDataAccessMySQL.class.inc で操作します。

bash-3.2$ cat MODEL/bbsDataAccessMySQL.class.inc 
<?php
    require_once('./COMMON/HttpApiRequest.class.inc');

    class bbsDataAccessMySQL {
        private $_db;

        const REQUEST_OK    = 0;
        const REQUEST_ERROR = 1;
        
        function __construct() {
            $this->_db = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT);
            $this->_db->query("SET NAMES utf8");
        }

        function __destruct() {
            $this->_db->close();
        }

        function saveBBS($item) {
            try {
                if($item['name'] == "" || $item['body'] == "") {
                    throw new Exception("param error");
                }
                $sql = "INSERT INTO bbs(name, body) values (?, ?)";
                if(($stmt = $this->_db->prepare($sql)) === false) {
                    throw new Exception("prepare error");
                }
                if(($stmt->bind_param("ss", $item['name'], $item['body'])) == false) {
                    throw new Exception("binding error");
                }
                if(($stmt->execute()) == false) {
                    throw new Exception("execute error");
                }
                $stmt->close();
                return self::REQUEST_OK;
            } catch (Exception $e){
                if($stmt) {
                    $stmt->close;
                }
                logs::error($e->getMessage());
                return self::REQUEST_ERROR;
            }
        }

        function listBBS() {
            try {
                $sql = "SELECT * FROM bbs order by id desc";
                if(($result = $this->_db->query($sql)) == false) {
                    throw new Exception("execute error");
                }
                $res = array();
                while($row = $result->fetch_array(MYSQLI_ASSOC)) {
                    array_push($res, $row);
                }
                return $res;
            } catch (Exception $e) {
                logs::error($e->getMessage());
                return array();
            }
        }
    }
?>

最後にSmartyのtemplateですが、今回定義したのはメイン画面のmain.tplだけです。その中身も実際は

bash-3.2$ more VIEW/main.tpl 
{include file='header.tpl'}

{include file='form.tpl'}
{include file='list.tpl'}

{include file='footer.tpl'}

です。 form.tpl が投稿フォームの部分, list.tplはリスト部分を定義しています. list.tpl

bash-3.2$ cat VIEW/list.tpl 
<table  width="500px;">
{foreach from=$p.bbs item=data}
    <tr><td>
    {$data.name|escape:'html'}
    </td><td>
    {$data.pubdate}
    </td></tr>
    <tr><td colspan="2">
    {$data.body|escape:'html'|nl2br}
    </td></tr>
{/foreach}
</table>

です。 $data.name $data.bodyの部分は Smarty変数の修飾子を用いて加工しています。escapeではXSS対策をnl2brは改行をbrタグに置換する処理です。Smartyでhそれ以外にもプラグインという形で変数をtemplate側で加工することが可能です。必要に応じて使うと有効です。

今回のプログラム一式は
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog9
にありますので、必要に応じて参照してください。

【演習】
前回の、地図のAPIと組み合わせて、場所も特定できる掲示板を作成してみましょう。
DBの保存情報に緯度経度を加えます。ポイントは地図を選択した際に form の input hidden の値を書き換えるJavaScriptを書くことが必要になるでしょう. Ajaxで一気に投稿する拡張をすればさらにステップアップできます。