ZendFrameworkで作る『イマドキ』のWebアプリケーション

第3回じめてのZendFrameworkアプリケーション

早速ですが、はじめてのZendFrameworkのアプリケーションを作ってみましょう。

何事をはじめるにも最初は簡単なことからはじめるのが一番です。ゲストブックと呼ばれるアプリケーションを作ってみましょう。

ゲストブックとは

旅館やペンションにて、宿泊客が思い思いの感想を書くノートなどを見かけたことがありませんか? 感想を書いたことがある方もいることでしょう。このノートはゲストブックと呼ばれています。

ゲストブックは設計が非常にシンプルなのでコンピュータアプリケーションの入門アプリとして広く利用されています。この連載でも最初のアプリケーションはゲストブックにします。

フレームワークなしのゲストブックアプリ

Zend Frameworkを利用したゲストブックアプリケーションを期待されているかもしれませんが、ちょっと我慢してください。まず最初に、通常のPHPアプリとして作られたゲストブックアプリを見てみましょう。

ゲストブックの仕様

どんなアプリケーションでも仕様が必要です。今回作るゲストブックは以下の仕様をみたすものとします。

  • ゲストブックは誰でも記入できる
  • タイトル、メッセージ、記入日時を記録する
  • 一覧が参照できる
  • セキュリティは特に考慮しない
  • エラー処理は考慮しない
  • 可能な限り実装を簡略化する

はじめてのZend Frameworkアプリケーションの元となるPHPアプリケーションは、安全なアプリケーションでないことに注意してください。安全でない、というよりは穴だらけなうえ、必要な処理が省略されているダメなアプリケーションです。

通常このような実装での公開は大いに問題がありますが、セキュリティやエラー処理など細かい部分の解説は理解の妨げになるため、敢えて省略しています。順次問題点は解説しますのでご安心ください。

ゲストブックアプリファイルの配置

ファイルは/www/guestbookに置くことにします。Zend Frameworkのアプリケーションと通常のPHPアプリケーションは共存できるので、同じアプリケーションのディレクトリ内に配置することにします。

自分ですべてのファイルやディレクトリを作成することも可能です。Zend Frameworkは特定のファイルレイアウトを想定していないので、どのよなディレクトリ構成でも可能です。今回はZend Tool(ファイルの生成ツール)で初期化したプロジェクトのディレクトリとファイルを使うことにします[1]⁠。

Zend Toolで作成済みのsample applicationディレクトリを/www/guestbookとしてコピーするか、zfコマンドが使える環境にあるなら、以下のコマンドを実行します。

mkdir /www/guestbook
cd /www/guestbook
zf project create

前回の解説通りPostgreSQLバイナリ版をインストールしているのであれば、/www/guestbookから/www/defaultへのシンボリックリンクを作るだけで/www/guestbookアプリケーションのディレクトリが利用されるようになっているはずです。

シンボリックリンクの作成
cd /www
ln -s guestbook default

この状態でZend Frameworkの空アプリケーションが動作するか確かめておきましょう。

Hello from the index view script.

と表示されれば正しく動作しています。

ゲストブックデータベースの作成

データベースを作成するスクリプトは用意していないのでコマンドで作成します。データベース作成用のSQLコマンドファイルは次の通りです。

guestbook.sql
CREATE TABLE guestbook (id SERIAL PRIMARY KEY, title TEXT, content TEXT, date_created TIMESTAMP);

どこでもよいのでguestbook.sqlファイルを作成したあと、次のコマンドを実行します。

/opt/PostgreSQL/8.3/bin/createdb -U postgres guestbook
/opt/PostgreSQL/8.3/bin/psql -U postgres -f guestbook.sql guestbook

これでデータベースが作成できました。

備考:この方法ではPostgreSQLのスーパーユーザであるpostgresアカウントに所有されたデータベースが作成されます。運用時にはスーパーユーザをサービスに利用することは好ましくありません。

通常版ゲストブックアプリ

通常版のゲストブックアプリはDocumentRootに配置するだけです[2]⁠。ディレクトリが2つ、ファイルが3つの非常に簡単なアプリケーションです。データベースにPostgreSQLを利用しています。

画像
ゲストブックの一覧
ゲストブックの一覧
ゲストブックの記入
ゲストブックの記入
guestbook/index.php
<?php
include 'list/index.php';

このファイルはゲストブックの一覧を表示するスクリプトを呼び出しすだけのスクリプトです。

guestbook/list/index.php
<?php
// ゲストブックデータベースを開く
$db = new PDO('pgsql:host=localhost dbname=guestbook user=postgres password=postgresql');
// すべてのエントリを取得
$stmt = $db->prepare('SELECT * FROM guestbook ORDER BY id DESC');
$stmt->execute();
$rows = $stmt->fetchAll();
// データベースオブジェクトを削除
$db = null;

?>
<html>
<head>
<title>ゲストブック - 一覧</title>
</head>
<body>
<p><a href="../post/">ゲストブックに記入する</a></p>
<?php if (empty($rows)): ?>
<p>ゲストブックが空です。</p>
<?php else: ?>
<table>
     <?php foreach ($rows as $row): ?>
     <tr><td>
    日時: <?php echo htmlentities($row['date_created']) ?><br />
     件名:<?php echo htmlentities($row['title']) ?> <br />
     メッセージ: <?php echo htmlentities($row['content']) ?><br />
     </td>
     </tr>
     <?php endforeach; ?>
</table>
<?php endif; ?>
</body>
</html>

このファイルはシンプルなPHPアプリケーションによくある、一つのファイルですべての処理を済ませるスクリプトです。データベースを開き、中身をすべて読み込み、全部一度に出力しています。

guestbook/post/index.php
<?php
try
{
    // ゲストブックデータベースを開く
    $db = new PDO('pgsql:host=localhost dbname=guestbook user=postgres password=postgresql');

    empty($_POST) ? define('POST',false) : define('POST', true);

    // フォームが送信されている
    if (POST) {
        // データベースに保存
        $db->query("INSERT INTO guestbook (title, content, date_created) VALUES (".$db->quote($_POST['title']).", ".$db->quote($_POST['content']).", ".$db->quote(date('Y-m-d H:i:s')).")");
    }
} catch (PDOException $e) {
    // データベースにアクセスできない
    die('DBサーバへアクセス可能か確認してください');
}


?>
<?php if (!POST): ?>
<html>
<head>
<title>ゲストブック - 書き込み</title>
</head>
<body>
<div>
<form id="guestbook" action="<?php echo $_SERVER['PHP_SELF'] ?>" method="post">
<div>
件名: <input type="text" name="title" id="title" /><br />
</div>
<div>
メッセージ:<textarea name="content" id="content"></textarea><br />
</div>
<div>
<input type="submit" name="送信" id="submit_btn" />
</body>
</html>
<?php else: ?>
<html>
<head>
<title>ゲストブック - 書き込み</title>
</head>
<body>
<div><p><a href="../list/">一覧に戻る</a></p></div>
<div>
件名: <?php echo htmlentities($_POST['title']) ?><br />
</div>
<div>
メッセージ:<?php echo htmlentities($_POST['content']) ?><br />
</div>
</body>
</html>
<?php endif; ?>

このスクリプトはデータベースを開き、送信された情報を書き込むスクリプトです。

このアプリケーションを見て「これは酷いアプリだ」とすぐ気がつく方は経験を積んだプログラマだと思います。⁠酷いアプリだ」と気がつかなかった方、大丈夫です。どこがダメなのか解説しながらアプリを改善していきます。

その前に、このアプリケーションをZend Frameworkを利用した書き直してみましょう。

Zend Framework版のゲストブックアプリ

通常版アプリケーションをできる限りそのままに、Zend Framework版のゲストブックアプリケーションを作ってみましょう。言い換えれば、Zend Frameworkアプリケーションらしくないアプリですが、Zend Frameworkを使ったアプリ書き直してみましょう、ということです。

Zend Frameworkアプリケーションと通常のアプリケーションは共存できるので、同じアプリケーションディレクトリの中に作ることにします。

Zend Toolのzfコマンドによって、Zend Framework本体や公開用のディレクトリ、Zend Frameworkアプリケーションを保存するディレクトリが作成されていると思います。本当はいろいろ解説すべき部分があるのですが、とにかくゲストブックアプリを作ってみましょう。

必要なファイルは、コントローラスクリプト、ビュースクリプトの2種類、7つのファイルです。以下の図は追加か修正が必要なスクリプトのみを記載しています。

画像

まずはエラー処理

1.7.xのzfコマンドはエラー処理用に必要なコードとビュースクリプトを用意していません。これでは例外が発生したとき空のページが表示され、何が問題なのかまったく分かりません。

幸い簡単なコード追加でエラーを表示させることができます。簡単なエラー処理を行うにはerrorコントローラクラスにerrorアクションを追加し、errorビューを追加するだけす。

/www/guestbook/application/controllers/ErrorController.phpに次のメソッドを追加し、

    public function errorAction()
    {
    }

このerrorアクション用のビューを作ります。

application/views/scripts/error/error.phtml
<html>
<head><title>Error</title></head>
<body>
<h1>An error occurred</h1>
<h2><?php echo $this->message ?></h2>
<?php if ('development' == $this->env): ?>
<h3>Exception information:</h3>
<p>
 <b>Message:</b> <?php echo $this->exception->getMessage() ?>
</p>
<h3>Stack trace:</h3>
<pre><?php echo $this->exception->getTraceAsString() ?></pre>
<h3>Request Parameters:</h3>
<pre><?php var_dump($this->request->getParams()) ?></pre>
<?php endif ?>
</body>
</html>

このビューを追加すると、エラーが発生した場合になぜエラーになったか分かるようになります。

<?php if ('development' == $this->env): ?>

から、開発環境の時だけ詳細なエラーメッセージが表示されるようになっていることが分かると思います。

これで例外エラーは表示されるようになりますが、通常のPHPエラーは表示されていないかも知れません。本連載で解説したインストール方法のphp.iniでは、display_errors=Offに設定されています。この設定ではエラーは表示されません。運用時にはこれでよいのですが、開発時には困るので、php.iniを修正するか、スクリプト中で設定を変更しなければなりません。PHPエラーが表示されないと、つまらない間違い(関数名のタイプミス等)で時間を浪費してしまうことも多いので、開発環境ではdisplay_errors=On、error_reporting=E_ALLに設定するとよいでしょう。

本連載のインストールでのphp.iniの場所は次の通りです。

  • /opt/PostgreSQL/EnterpriseDB-ApachePhp/php/php.ini

php.iniの場所を知るにはphpinfo関数を実行するとよいでしょう。

画像

エラー処理用のビューを追加すると、存在しないコントローラを呼び出した等の例外が発生した場合にその状況がエラーメッセージと共に表示されるようになります。

画像

この図は、http://localhost/hoge/hoge/ のように、存在しないhogeコントローラのhogeアクションを呼び出そうとした時のエラー画面です。

これで随分、アプリケーションを作ることが楽になります。

ListとPostコントローラ/ビュースクリプトの作成

次にゲストブックアプリケーションの中身であるListとPostのコントローラとビュースクリプトを作ります。

まずはListコントローラから作りましょう。Zend Frameworkの機能についても一つづつ紹介します。

ZendFrameworkはあまり規約を設定に利用していないフレームワークですが、コントローラファイルのファイル名、コントローラクラスの名前、アクションメソッド名は規約通りに付けなければなりません。

コントローラスクリプトはコントローラディレクトリ(この場合、/www/guestbook/application/controllers)に配置しなければなりません。ファイル名は

  • [コントローラ名]Controller.php

でなければなりません。コントローラファイルはコントローラクラスを定義する必要があります。クラス名は

  • [コントローラ名]Controller

でなければなりません。コントローラクラスにはそのコントローラがサポートするアクションをメソッドとして定義しなければなりません。アクションメソッド名は

  • [アクション名]Action

でなければなりません。デフォルトでは

  • http://hostname/[コントローラ名]/[アクション名]/

とリクエストされた場合、[コントローラ名]Controllerクラスの[アクション名]Actionが呼び出されます。

Zend Frameworkのコントローラについてほかにも知るべきことはありますが、今のところはこれだけ知れば十分です。

ListController.php
<?php

class ListController extends Zend_Controller_Action
{

    public function init()
    {
        /* Initialize action controller here */
                $this->db = Zend_Db::factory('Pdo_Pgsql', array(
                        'host'          => 'localhost',
                        'username'      => 'postgres',
                        'password'      => 'postgresql',
                        'dbname'        => 'guestbook'
                ));
    }


    public function indexAction()
    {
        /* Default action for action controller */
                $this->_forward('List');
    }

        public function listAction()
        {
                $sql = "SELECT * FROM guestbook ORDER BY id DESC";
                $rows = $this->db->fetchAll($sql);

                $this->view->rows = $rows;
        }


}

initメソッドはコントローラオブジェクトが作成される場合に自動的に実行させるメソッドです。initメソッドの

                $this->db = Zend_Db::factory('Pdo_Pgsql', array(
                        'host'          => 'localhost',
                        'username'      => 'postgres',
                        'password'      => 'postgresql',
                        'dbname'        => 'guestbook'
                ));

でZend_Dbデータベースオブジェクトを作成しています。Pdo_Pgsqlを利用してPostgreSQLデータベースへの接続を作りました。

indexActionが呼ばれたとき、つまり

  • http://localhost/list/

とリクエストされた時には、自動的にIndexController.phpのindexActionが呼び出されるようになっています。

このアプリケーションではindexActionには特にすることがないのでlistActionに処理を渡すことにしています。そのために

$this->_forword('List');

を実行してlistActionに処理を渡しています。結果として、ユーザは

  • http://localhost/list/
  • http://localhost/list/list/

とアクセスしても同じ結果になります。indexActionに特定の機能を持たせたくない場合や、将来別の機能を持たせるかもしれない場合に_forward()は便利です。

次にビュースクリプトの解説をしますが、デフォルトのビュースクリプト名はアクション名になっています。アクション名に意味のある名前を持たせるとビュースクリプトのファイル名も分かりやすくなります。

listアクションでは

                $sql = "SELECT * FROM guestbook ORDER BY id DESC";
                $rows = $this->db->fetchAll($sql);

                $this->view->rows = $rows;

で、SQL文を実行してすべてのレコードを取り出し、コントローラがあらかじめ用意してくれているviewオブジェクトのプロパティとして保存しています。

この状態で一度このコントローラにアクセスしてみましょう。

ビュースクリプトが無い場合のエラー

Message: script 'list/list.phtml' not found in path (../application/views/scripts/)

と、ビュースクリプトがないため、エラーになっていることが分かります。

ビュースクリプトの名前から分かるように、Zend Frameworkのビューは通常のPHPスクリプトとして処理されます。つまり、if、while、echo などのPHPの制御文や関数が通常通り使えることを意味します。

ビュースクリプトを作る

早速ビュースクリプトを作ってみましょう。ビュースクリプトは

  • /www/guestbook/application/views/scripts

ディレクトリに中に、コントローラ名のディレクトリを作成し、その中に保存します。デフォルトのビュースクリプト名はアクション名と同じです。拡張子はphtmlです。

Listkコントローラのlistアクションのビュースクリプトの場所と名前は次のようになります。

  • /www/guestbook/application/views/scripts/list/list.phtml
/www/guestbook/application/views/scripts/list/list.phtml
<html>
<head>
<title>ゲストブック - 一覧</title>
</head>
<body>
<p><a href="../post/">ゲストブックに記入する</a></p>
<?php if (empty($this->rows)): ?>
<p>ゲストブックが空です。</p>
<?php else: ?>
<table>
 <?php foreach ($this->rows as $row): ?>
  <tr>
  <td>
   日時: <?php echo htmlentities($row['date_created']) ?><br />
   件名:<?php echo htmlentities($row['title']) ?> <br />
   メッセージ: <?php echo htmlentities($row['content']) ?><br />
   </td>
  </tr>
 <?php endforeach; ?>
</table>
<?php endif; ?>
</body>
</html>

$thisとはviewオブジェクトのことです。listコントローラのlistアクションで

 $this->view->rows = $rows;

として代入された$rowsが$this->rowsとして利用できます。

ビューを定義後にアクセスすると次のようなページが表示されます。

ゲストブックの一覧

Postコントローラとビュー

Postコントローラとビューについては、ファイルを簡単に紹介します。

/www/guestbook/application/controllers/PostController.php
<?php
class PostController extends Zend_Controller_Action
{

    public function init()
    {
        /* Initialize action controller here */
        $this->db = Zend_Db::factory('Pdo_Pgsql', array(
            'host'      => 'localhost',
            'username'  => 'postgres',
            'password'  => 'postgresql',
            'dbname'    => 'guestbook'
        ));
        date_default_timezone_set('Asia/Tokyo');
    }


    public function indexAction()
    {
        /* Default action for action controller */
        $this->_forward('post');
    }

    public function postAction()
    {
        if (!$this->getRequest()->isPost()) {
            $this->_forward('edit');
        } else {
            $date = date('Y-m-d H:i:s');
            $db = $this->db;
            // データを保存する
            $sql = "INSERT INTO guestbook (title, content, date_created) VALUES (".$db->quote($_POST['title']).", ".$db->quote($_POST['content']).", ".$db->quote($date).");";
            $db->query($sql);
        }
    }

    public function editAction()
    {
    }


}

目に付くのはdate_default_timezone_set関数とRquestオブジェクトの利用くらいだと思います。

PHP 5.2から新しくロケール対応機能が追加されたため、タイムゾーンを指定しないで日付関数などを利用するとPHPエラーが発生します。これを防ぐためにdate_default_timezone_set関数を利用してタイムゾーンを日本(Asia/Tokyo)に設定しています。

このコントローラはフォームから送信されたデータを受信するので、RequestオブジェクトのisPostメソッドを利用してフォームがPOSTされているか確認しています。

        if (!$this->getRequest()->isPost()) {
            $this->_forward('edit');
        } else {

フォームが送信されていない場合は、editActionのフォワードして編集用のビューが表示されるようにしています。

フォームがポストされた場合は、Zend_Dbオブジェクトを利用して、送信されたデータをデータベースに保存しています。

/www/guestbook/application/views/scripts/post/edit.phtml
<html>
<head>
<title>ゲストブック - 書き込み</title>
</head>
<body>
<div>
<form id="guestbook" action="<?php echo $this->url() ?>" method="post">
<div>
件名: <input type="text" name="title" id="title" /><br />
</div>
<div>
メッセージ:<textarea name="content" id="content"></textarea><br />
</div>
<div>
<input type="submit" name="送信" id="submit_btn" />
</body>
</html>

edit.phtmlについては、PHPコードをまったく含まないHTMLファイルですので、省略します。

/www/guestbook/application/views/scripts/post/post.phtml
<html>
<head>
<title>ゲストブック - 書き込み</title>
</head>
<body>
<div><p><a href="<?php echo $this->url(array('controller'=>'list')) ?>">一覧に戻る</a></p></div>
<div>
件名: <?php echo htmlentities($_POST['title']) ?><br />
</div>
<div>
メッセージ:<?php echo htmlentities($_POST['content']) ?><br />
</div>
</body>
</html>

post.phtmlは一覧のページ、つまりlistコントローラのlistアクションに戻るURLを作るためにurlメソッドを利用しています。

<div><p><a href="<?php echo $this->url(array('controller'=>'list')) ?>">一覧に戻る</a></p></div>

これは

<div><p><a href="/list/">一覧に戻る</a></p></div>

と書いてしまったほうが短くて分かりやすいのですが、場所が変ったりした場合に書き換えなければならくて不便です。urlメソッドを利用すれば、後になって解説するモジュールを使ってURLが変った場合でも書き換える必要がありません。

まとめ

どうでしょうか? はじめてのZend Frameworkアプリケーションは動きましたか? まだまだ、Zend Frameworkの機能を活かしたアプリケーションとはいえませんが、どのような感じでアプリケーションを作るのか感触はつかめたと思います。

最終的には本格的なブログアプリケーションを作ることに挑戦しますが、しばらくはこの簡単なゲストブックアプリケーションを改良していくことで、Zend Frameworkアプリケーションの作り方を習得していきます。

おすすめ記事

記事・ニュース一覧