PHPオブジェクト指向実践

メッセージボード

クラスの設計

メッセージボードを作ってみます。
今回作るメッセージボードは以下のような仕様とします。

  • 掲示板のような形式
  • 1行メッセージボードとし、複数行の書き込み不可
  • 返信機能なし
  • データはCSV形式(カンマ区切りテキスト)でテキストファイルに保存する

以上を踏まえてまずはクラスを設計していきます。

MessageBoard.php
<?php

class MessageBoard
{
    protected $dataFilePath;

    // データファイルのパス設定
    public function setDataFilePath()
    {
    }

    // メッセージデータ取得
    public function getMessagesData()
    {
    }

    // 書き込み処理
    public function write()
    {
    }
}

?>

必要な処理は、

  • 現在書き込まれているメッセージの表示用のデータ取得
  • 新たなメッセージの保存
  • メッセージを保存するテキストファイルの指定

というところです。
簡易カウンターのところではデータ保存ファイルを定数で設定していましたが、
今回は変数として、クラス外部より保存ファイルを自由に設定できるようにします。

これは、このメッセージボードクラスを汎用的に使用できるようにしたい為です。
例えばサイト上で話題ごとにメッセージボードを分けて複数作りたいような場合、
保存ファイルを変えることで、同じクラスを別々のメッセージボードに使いまわすことが出来ます。


ということでメソッドの中身を実装していきます。

データファイルのパス設定メソッドの実装

まずメッセージデータを保存するテキストファイルのパスを設定するメソッドです。

    // データファイルのパス設定
    public function setDataFilePath($path)
    {
        if (false == file_exists($path)) {
            if (false == @touch($path)) {
    	        throw new @CException@(\"エラーが発生しました\");
    	    }
        }
        $this->dataFilePath = $path;
    }

このメソッドでやっていることは、
引数で指定されたパスをクラスのフィールドに設定していることです。

設定する前に、そのファイルが実際に存在するかチェックをしています。
もし存在しなければ、ファイルを新規作成しています。

以後、データ取得や保存などの各メソッドでは、
ここで設定したパスのファイルを使用します。

メッセージデータ取得メソッドの実装

次に書き込みデータを保存しているテキストファイルよりデータを取得するメソッドを見ていきます。

    // メッセージデータ取得
    public function getMessageData()
    {
        // ファイルよりCSVデータ取得
        $fp = fopen($this->dataFilePath, \'r\');
        $data = array();
        while ($row= fgetcsv($fp)) {
            $data[] = $row;
        }
        fclose($fp);

        // 配列を逆転
        $data = array_reverse($data);
        return $data;
    }

メッセージデータはテキストファイルにCSV形式で保存されているため、
これを読み込み、2次元配列として取得します。

そしてメッセージデータは書き込みがあるたびに下へ下へ追加されて行っているので、
言ってみればそのまま取得すると日付の古い順に並びます。
なのでデータ取得後、配列の順序を逆転することで日付の新しい順に並べ替えています。

書き込み処理メソッドの実装

次に書き込み処理メソッドです。

    // 書き込み処理
    public function write($post)
    {
        // 新規番号取得
        $no = $this->getNewNo();

        // カンマを削除
        $name = str_replace(",", "", $post['name']);
        $message = str_replace(",", "", $post['message']);

        // 番号、投稿者名、メッセージをカンマ区切りで文字列結合
        $lineData = sprintf("%s,%s,%s¥r¥n", $no, $name, $message);
        
        // ファイルに書き込む
        $fp = fopen($this->dataFilePath, 'a');
        fputs($fp, $lineData . '¥r¥n');
        fclose($fp);
    }

    // 新規番号取得
    protected function getNewNo()
    {
        $data = $this->getMessageData();
        $no = 1;
        if (0 < count($data)) {
            $no = $data[0][0];
            $no++;
        }
        return $no;
    }

書き込みは、投稿者がサイト上のフォームの書き込みボタンをクリックすることで送信されるPOSTデータを基に処理を行います。

書き込み処理メソッドには引数として$_POSTをそのまま渡すことを想定します。

その上でまずPOSTデータをカンマ区切りで文字列結合し、データ保存ファイルに書き込むためのCSV形式の行データを作成します。

またメッセージデータは書き込みNoとして連番項目を持ちますので、
現在の最大の番号に+1した数値を書き込み用に取得する必要があります。
今回はこの処理を別メソッドにしました。これはクラス外部より直接実行できる必要はなないのでprotectedです。

そして書き込み番号とPOSTデータを基に書き込み用のCSVデータを作り、これをファイルの末尾に追加書き込みします。

保存データがカンマ区切り形式の為、書き込み内容にカンマが含まれるとおかしなことになります。なので、カンマは強制的に削除しています。

以上、全てのメソッドが実装された状態のクラスを見てみましょう。

MessageBoard.php
<?php

class MessageBoard
{
    protected $dataFilePath;

    // データファイルのパス設定
    public function setDataFilePath($path)
    {
        if (false == file_exists($path)) {
            if (false == @touch($path)) {
                throw new Exception("エラーが発生しました");
            }
        }
        $this->dataFilePath = $path;
    }

    // メッセージデータ取得
    public function getMessageData()
    {
        // ファイルよりCSVデータ取得
        $fp = fopen($this->dataFilePath, 'r');
        $data = array();
        while ($row= fgetcsv($fp)) {
            $data[] = $row;
        }
        fclose($fp);

        // 配列を逆転
        $data = array_reverse($data);
        return $data;
    }

    // 書き込み処理
    public function write($post)
    {
        // 新規番号取得@
        $no = $this->getNewNo();

        // カンマを削除
        $name = str_replace(',', '', $post['name']);
        $message = str_replace(',', '', $post['message']);

        // 番号、投稿者名、メッセージをカンマ区切りで文字列結合
        $lineData = sprintf("%s,%s,%s\r\n", $no, $name, $message);

        // ファイルに書き込む
        $fp = fopen($this->dataFilePath, 'a');
        fputs($fp, $lineData);
        fclose($fp);
    }

    // 新規番号取得
    protected function getNewNo()
    {
        $data = $this->getMessageData();
        $no = 1;
        if (0 < count($data)) {
            $no = $data[0][0];
            $no++;
        }
        return $no;
    }
}

?>

メインスクリプトの実装

message_board.php
<?php

require_once '../class/message_board/MessageBoard.php';

$mb = new @CMessageBoard@();
// データ保存ファイル設定
$datFile = '../data/message_board.dat';
$mb->setDataFilePath($datFile);

// 書き込みボタンが押された場合
if (true == isset($_POST['write'])) {
    // 書き込み処理
    $mb->write($_POST);
    // 更新ボタン等による2重処理防止の為リダイレクト
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

// メッセージデータ取得
$data= $mb->getMessageData();

?>

<html>
<head>
<style type="text/css">
input.msg {
    width:500px;
}
td {
    border:1px solid #555555;
}
</style>
<title>メッセージボード</title>
</head>
<body>

<form method="post" action="#">
名前:<input type="text" name="name" /><br />
メッセージ:<input type="text" name="message" class="msg" /><br />
<input type="submit" name="write" value="書き込む" />
</form>

<table style="border:1px;">
<?php
// メッセージデータをもとに表のHTML生成
foreach ($data as $row) {
    echo '<tr>';
    echo sprintf('<td>%s</td>', $row[0]);
    echo sprintf('<td>%s</td>', $row[1]);
    echo sprintf('<td>%s</td>', $row[2]);
    echo '</tr>';
}
?>
</table>

</body>
</html>

メインスクリプトです。

MessageBoardクラスのインスタンスを生成した後、
まずメッセージデータ保存ファイルのパスを設定します。

もし「書き込み」ボタンがクリックされてきた場合、
$_POST['write']が存在するので、この場合は書き込みメソッドを実行します。
引数として$_POSTをそのまま渡しています。
処理後、自URLにリダイレクトします。
書き込み処理後、そのまま処理を続けてページ表示を行った場合、
もしブラウザの「更新」ボタンが押されたら、もう一度書き込み処理が実行されてしまうためです。
$_POSTデータが送信されてきた状態で「更新」が押されると、$_POST情報も残っているためです。

最後にメッセージデータ取得メソッドの実行し、メッセージデータを配列で取得します。
この配列をforeachでループしながら、tableタグの内容を生成していくわけです。


というわけで今回作成したファイル群の階層図です。

public_html/
  ∟class/
    ∟message_board/
      ∟MessageBoard.php
  ∟data/
    ∟message_board.dat
  ∟message_board.php

再利用

冒頭でも触れたとおり、今回はデータ保存ファイルのパスを設定できるようにしまし、汎用性を持たせました。

基本的にクラスは固定値的な要素は無くし、出来る限り汎用的に設計すべきです。
例えば今回のMessageBoardクラスの場合、保存ファイルを変えることにより、複数のメッセージボードを設置することが可能です。

ファイル構成的には例えば以下のような感じになります。

public_html/
  ∟class/
    ∟message_board/
      ∟MessageBoard.php
  ∟data/
    ∟message_board.dat
    ∟impression_board.dat
  ∟message_board.php
  ∟impression_board.php


ホームページを見てもらった感想でも書き込んでもらおうと、
impression_board.phpを追加しました。
impression_board.phpの中身はmessage_board.phpと全く同じで、
そのままコピーして名前を変えるだけです。
そして唯一、設定するデータ保存ファイルのパスのみ変えます。
ファイルはimpression_board.datを指定します。

これにより、同じMessageBoardクラスを使用して処理をしていても、
参照しているデータは別のものなので、別々のメッセージボードとして機能します。

こういうところはクラスのメリットといえます。

基礎編で、クラスのインスタンスの特性として、フィールドという形で独自にデータを保持することが出来るということを言っていますが、
まさにそれを生かす例です。