phalcon-devtoolsを使用して簡易掲示板を作成する

気が付くともう12月半ばで [ニジボックス Advent Calendar 2015](http://qiita.com/advent-calendar/2015/nijibox) も良い感じに進んでますね。

今回は最近さわりはじめたPHPの爆速フレームワークphalconで
簡単なアプリケーションの作成について書きたいと思います。

#作成環境
- Vagrant
- CentOS6.7
- MySQL5.5
- phalcon2.08
- phalcon-devtools

#作るもの
- ひとこと掲示板
  - 名前とコメントをDBから拾ってきて表示
  - 名前とコメントを入力して投稿が行える
  - 投稿確定前に確認画面を入れる

<img src="https://qiita-image-store.s3.amazonaws.com/0/101638/a0ec36da-7fba-d4ee-78e3-9db765f74742.jpeg" alt="ひとこと掲示板" width="600" height="240" />

#Phalconフレームワークの特徴
主な特徴については以下の通りです。

- PHP拡張として実装されたフルスタックのフレームワーク
- 他のフレームワークと比べて非常に高速に動作する

※導入方法については[公式サイト](https://phalconphp.com/ja/download)を参照。

#Phalcon Developer Toolsとは

[phalcon-devtools](https://docs.phalconphp.com/ja/latest/reference/tools.html)はプロジェクトの雛形を自動生成してくれる便利なツールです。
手っ取り早く動くものが用意できるので、積極的に使っていきます。

#プロジェクトの作成

では、さっそく作っていきます。

ドキュメントルート下でプロジェクトの自動生成を行います。

```bash:
phalcon create-project bbs micro
```

プロジェクトのタイプには以下の3種類の構成から選べます。

- micro マイクロフレームワーク(小規模、プロトタイプ、API実装向け)
- simple 普通のMVC構成　
- module モジュール別のMVC構成　※[simpleとmoduleの違いについて](http://phalcon-docs-ja.readthedocs.org/ja/latest/reference/applications.html)

今回はシンプルなマイクロフレームワークを選択しています。

生成が完了すると、bbsアプリは以下の構成になると思います。

```bash:
.
└── bbs
    ├── app.php
    ├── config
    │   ├── config.php
    │   ├── loader.php
    │   └── services.php
    ├── index.html
    ├── migrations
    ├── models
    ├── public
    │   ├── css
    │   ├── files
    │   ├── img
    │   ├── index.php
    │   ├── js
    │   └── temp
    └── views
        ├── 404.phtml
        └── index.phtml
```
 
ブラウザでアクセスして以下のページが表示されていれば成功です。
![WS000002.JPG](https://qiita-image-store.s3.amazonaws.com/0/101638/2ba7578d-8d88-3a3f-84ea-ed78ace6c434.jpeg)

#DBの接続先設定

次にDB接続先の設定を行います。

```php:
#bbs/config/config.php
return new \Phalcon\Config(array(

    'database' => array(
        'adapter'    => 'Mysql',
        'host'       => 'localhost',
        'username'   => 'root',
        'password'   => '',
        'dbname'     => 'bbs', #データベース名をbbsに変更
        'charset'    => 'utf8',
    ),
```

#MySQLでスキーマを作成

MySQLに接続してbbsデータベースを作成します。

```bash:
mysql -u root
CREATE DATABASE bbs;
```

事前にテーブルも用意しておきます。(後でModelの自動生成に役立ちます)

```sql:
use bbs
CREATE TABLE posts(
	id INT NOT NULL AUTO_INCREMENT,
	name VARCHAR(20) NOT NULL,
	comment VARCHAR(100) NOT NULL,
	create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (id)
);
```

bbsデータベースとpostsテーブルができたら準備完了です。

#Modelの自動生成

bbsアプリ下に移動して以下のコマンドを実行します。

```bash:
cd bss/
phalcon create-model posts
```

すると、DBスキーマを元にModelが自動生成されました！

```php:
#bbs/models/Posts.php
class Posts extends \Phalcon\Mvc\Model
{

    /**
     *
     * @var integer
     */
    public $id;

    /**
     *
     * @var string
     */
    public $name;

    /**
     *
     * @var string
     */
    public $comment;

    /**
     *
     * @var string
     */
    public $create_date;

    /**
     * Returns table name mapped in the model.
     *
     * @return string
     */
    public function getSource()
    {
        return 'posts';
    }

    /**
     * Allows to query a set of records that match the specified conditions
     *
     * @param mixed $parameters
     * @return Posts[]
     */
    public static function find($parameters = null)
    {
        return parent::find($parameters);
    }

    /**
     * Allows to query the first record that match the specified conditions
     *
     * @param mixed $parameters
     * @return Posts
     */
    public static function findFirst($parameters = null)
    {
        return parent::findFirst($parameters);
    }

}
```

Postsモデルに関しては find() 関数があれば十分なので、特に修正する箇所はありません。

#ルーティングの設定

micro アプリケーションでは app.php でルーティングの設定が記載されています。
トップページ(/)を修正して、確認ページ(/confirm)と投稿完了(/complete)の設定を追加しました。
今回は規模が小さいため app.php に処理を書いていますが[Controllerを使用する方法](https://docs.phalconphp.com/ja/latest/reference/micro.html#id17)もあるようです。

```php:
#bbs/app.php
/**
 * Local variables
 * @var \Phalcon\Mvc\Micro $app
 */

/**
 * Add your routes here
 */

//トップページ
$app->get('/', function () use ($app) {
	//DBから投稿内容を新着順で取得
	$posts = Posts::find(array("order" => "create_date DESC"));

	//index.phtmlを表示
    echo $app['view']->render('index', array(
    	'posts' => $posts
    ));
});

//確認ページ
$app->post('/confirm', function () use ($app) {
	//リクエストのPOSTデータを取得
	$post = $app->request->getPost();
	
	//confirm.phtmlを表示
	echo $app['view']->render('confirm', array(
		'name' => $post['name'],
		'comment' => $post['comment']
	));
});

//投稿完了
$app->post('/compleate', function () use ($app) {
	//リクエストのPOSTデータを取得
	$post = $app->request->getPost();

	//決定ボタンが押されていたらDBに投稿内容を保存
	if ($post['submit'] === 'ok') {
		$bbs = new Posts();
		$bbs->save($post, array('name', 'comment'));
	}

	//トップページへリダイレクト
	$app->response->redirect("/")->sendHeaders();
});


/**
 * Not found handler
 */
$app->notFound(function () use ($app) {
    $app->response->setStatusCode(404, "Not Found")->sendHeaders();
    echo $app['view']->render('404');
});
```

POSTデータの受け取り、DBからデータの取得と保存の一連の流れが上記から分かるかと思います。

#テンプレートエンジンVoltの有効化

基本的にViewに記載する処理は素のPHPで書けますが、
[Volt: テンプレートエンジン](https://docs.phalconphp.com/ja/latest/reference/volt.html)を利用することで処理が短く、見た目が美しく書けます。
記法はSymfonyのTwigに似てると思います。

```php:
#bbs/config/services.php
$di = new FactoryDefault();

/**
 * Sets the view component
 */
$di->setShared('view', function () use ($config) {
    $view = new View();
    $view->setViewsDir($config->application->viewsDir);
    //テンプレートエンジンVoltを有効にする
    $view->registerEngines(
        array(
            ".phtml" => "Phalcon\Mvc\View\Engine\Volt"
        )
    );
    return $view;
});
```

上記の内容で *.phtml ファイルをVoltファイルに設定しています。

Voltが有効になると .phtml はブラウザに返されず .phtml.php というファイルに
コンパイルされてから返されるようになります。( .phtml.php は直接弄らないでください)

#トップページの作成

次に掲示板のトップページを作ります。

PhalconではViewファイルの拡張子が.htmlではなくて.phtmlです。
最初からCSS Bootstrapが有効になっているので活用することにします。

index.phtmlを修正して、名前とコメントを投稿するフォームタグと
投稿内容を表示するテーブルタグを追加しました。

投稿内容についてはVoltの[フィルタ](https://docs.phalconphp.com/ja/latest/reference/volt.html#id5)でエスケープしています。

```html:
<!-- bbs/views/index.phtml -->
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>トップページ</title>
    </head>
    <body>
        <div class="container">

            <div class="page-header">
                <h1>ひとこと掲示板</h1>
            </div>

            <form action="confirm" method="post">
                <div class="form-group">
                    <label>name</label>
                    <input type="text" class="form-control" name="name" value="">
                </div>
                <div class="form-group">
                    <label>comment</label>
                    <input type="text" class="form-control" name="comment" value="">
                </div>
                <button type="submit" class="btn btn-default">投稿</button>
            </form>

            <br>

            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>name</th>
                        <th>comment</th>
                        <th>create_date</th>
                    </tr>
                </thead>
                <tbody>
                    {% if posts is defined %}
                        {% for post in posts %}
                            <tr>
                                <td>{{ post.name|e }}</td>
                                <td>{{ post.comment|e }}</td>
                                <td>{{ post.create_date|e }}</td>
                            </tr>
                        {% endfor %}
                    {% endif %}
                </tbody>
            </table>
        </div>
    </body>
</html>
```

#確認ページの作成

投稿内容を再度表示して、確認を促すページを作ります。
サブミットボタンが押されると complete へ遷移します。その先の処理は app.php に書いてある通りです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/101638/8e66b32f-db3b-af7a-01d3-8bd7f57397b3.jpeg" alt="確認ページ" width="585" height="156" />

```html:
<!-- bbs/views/confirm.phtml -->
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>投稿内容の確認</title>
    </head>
    <body>
        <div class="container">

            <div class="page-header">
                <h1>ひとこと掲示板</h1>
            </div>

            <div class="text-center">            
                <h3>この内容で投稿しますか？</h3>
            </div>

            <br>

            <form action="compleate" method="post" class="form-horizontal">
                <input type="hidden" name="name" value="{{ name|e }}">
                <input type="hidden" name="comment" value="{{ comment|e }}">
                <div class="form-group">
                    <label class="col-sm-3 control-label">name</label>
                    <div class="col-sm-9">
                        <p class="form-control-static">{{ name|e }}</p>
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-3 control-label">comment</label>
                    <div class="col-sm-9">
                        <p class="form-control-static">{{ comment|e }}</p>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-sm-offset-3 col-sm-9 text-right">                      
                        <button type="submit" name="submit" value="no" class="btn btn-default">キャンセル</button>
                        <button type="submit" name="submit" value="ok" class="btn btn-danger">決定</button>
                    </div>  
                </div>
            </form>

        </div>
    </body>
</html>
```

#完成と総括

これで、掲示板への表示・投稿・確認ページの機能が実装されました。
見た目上それっぽく動くようになったかと思います。

後は、バリデーションやエラー表示、脆弱性対策など細かいところの改修が必要ですが
~~(力尽きた)~~ 記事の量が多くなってしまったので、また別の機会にしようと思います。

感想として、Phalconは意外とすんなり書けるので楽しかったです。
むしろ環境構築や導入のほうに時間がかかっていました…。

今後、仕事でも使える機会があるといいな。。

以上