Silexを試してみた

シンプルな問い合わせフォームアプリをSilexを使って作ってみましたのでその紹介。

Silexとは?

SilexとはPHPで書かれたマイクロフレームワークです。RubyのsinatoraにインスパイアされてSymfony2のリードマージャー(Lead Merger)であるファビアンさんが作っています。特徴として名前空間がしがし使っていたりするのでPHP5.3.2以上でないと動きません。

というと、Symfony2っぽいと思われる方がいると思いますが、このSilexのライブラリはSymfony2でも利用しているコンポーネント類の一部を利用して作成されています。いわばSymfony2の妹分といったところでしょうか。

あと、ライブラリをpharアーカイブで配布しているので基本silex.pharの1ファイルさえあれば動かせます。

冗談のようなアプリかと思っていたら意外(失礼)と本気のようで公式サイトができました。
http://silex-project.org/

ちょっとしたアプリをさくっと作りたい場合に良いのではということで実際に作ってみました。

MameForm

詳しいインストール方法は GitHub - brtriver/MameForm: simple contact form app based on Silex, Twig, SwiftMailerで。

インストール
git clone git://github.com/brtriver/MameForm.git ./MameForm
git submodule init
git submodule update

名前、メールアドレス、メッセージを入力し、指定したメールアドレスに送信するそれだけです。
テンプレートエンジンにTwig, メール送信にSwiftMailer4を使っています。
これらのライブラリはサブモジュール化しているのでgit submoduleコマンドで同期してください。

ということは、日本語メール送ると文字化けするかもですが。まぁサンプルですので。

構造

Twig,SwiftMailerのファイルは多いですが、それを除けば以下だけ。

処理のすべてはindex.phpに書かれています。viewsはTwigのテンプレート置き場です。
.htaccessがいっぱいあるのはドキュメントルート以下におかれてもdenyするためとindex.phpへのリライト設定のためです。

処理(index.php)

準備

今回はTwigやSwiftMailerを使うので記述が増えていますがそれらを除くと必要なのは以下だけです。

<?php
require __DIR__.'/silex.phar';
use Silex\Application;

$app = new Application();
....

pharアーカイブのおかげですね。

問い合わせ画面

http://localhost/MameForm/ようにアクセスすれば以下のような問い合わせ画面がでます。

Silexの該当処理をみてみます。

<?php
...
// entry
$app->get('/', function() use ($app) {
    return $app['twig']->render('entry.twig', array());
});

無名関数のおかげでシンプルです。"/"へのGETメソッドでのアクセス時にこの処理が行われます。
フォームを表示するだけなのでentry.twigテンプレートを表示するだけですね。

バリデーション

Silexにはバリデーションの機能はありません。自分で用意するか書けば良いのですが今回は以下のようにしてみました。

<?php
...
// send email
$app->post('/', function() use ($app) {
	// validate
	$errors = array();
	// check empty
	foreach (array('name', 'email', 'message') as $k) {
		if (trim($app['request']->get($k)) === "") {
			$errors[] = sprintf("%s is required.", $k);
		}
	}
	// check email
	if (!filter_var($app['request']->get('email'), FILTER_VALIDATE_EMAIL)) {
		$errors[] = sprintf("email is not valid.");
	}
	// send email
	if (count($errors) === 0) {
          //..メール送信処理後にリダイレクト
	}
	return $app['twig']->render('entry.twig', compact('errors'));
});

問い合わせ画面と同じ"/"へのリクエストですが、POSTメソッドでアクセスされたときに処理される点($app->post())であることが異なります。

$app['request']->get('リクエストパラメータ名')でPOSTされたデータを受けることができるので、これを利用して必須チェックとメールアドレスチェックを行っています。メールアドレスのチェックはPHP5.2から利用できるfilter_var関数を使っています。そして、エラーがあれば$errorsに配列でエラーメッセージを入れてエラーがある場合は再度問い合わせ画面に戻るという流れです。

エラー時のvalue属性の値の設定

テンプレートはTwigを使っていますが、appにSilexのアプリケーションインスタンスが格納されているのでそれを呼び出せば大抵のパラメータにもアクセスできます。

なので、POSTされたnameパラメータにアクセスしたい場合は {{app.requset.request.get('name')}}とすればアクセスできますが、.....ちょっと長ったらしいです。

そこで、Silexのアプリケーションのbeforeフィルター機能を使って、requestというグローバル変数にapp['request']->requestパラメータをアサインしておきます。

<?php
...
// filter
$app->before(function() use($app){
	// assign request parameters to "request" for Twig templates with shorter name
	$app['twig']->addGlobal('request', $app['request']->request);
});

これで、entry.twigは以下のようになりました。

{% extends "base.twig" %}
{% block title %}Entry Page{% endblock %}
{% block content %}

  <h1>Contact Form</h1>

  <ul class="error">
{% for error in errors %}
    <li>{{error}}</li>
{% endfor %}
  </ul>

  <div id="entryForm">
      <form method="post" action="{{app.request.baseUrl}}/">
	    <div id="formName">
          <label for="Name">Name:</label>
          <input type="text" name="name" id="Name" value="{{request.get('name')}}">
        </div>
        <div id="formEmail">
          <label for="Email">Email:</label>
          <input type="text" name="email" id="Email" value="{{request.get('email')}}">
        </div>
	    <div id="formMessage">  
          <label for="Message">Message:</label><br>
          <textarea name="message" rows="20" cols="20" id="Message">{{request.get('message')}}</textarea>
         </div>
         <div>
          <input type="submit" name="submit" value="Submit" class="submit-button">
         <div>
      </form>
  </div>
{% endblock %}

もちろん、Twigを使っているので標準でエスケープ処理されているのでXSSの心配はありません。

メール送信

Silexのドキュメントにあったサンプルとほぼ同じです。ただメール本文をTwigのテンプレートで書けるようにしただけです。

<?php
...
	// send email
	if (count($errors) === 0) {
	    $body = $app['twig']->render('mail.twig'); // twigのテンプレートの描画結果を代入
	    $message = \Swift_Message::newInstance()
	        ->setSubject(EMAIL_SUBJECT)
	        ->setFrom(array($app['request']->get('email')))
	        ->setTo(array(EMAIL_ADDRESS_FROM))
	        ->setBody($body);
	    $transport = \Swift_MailTransport::newInstance();
	    $mailer = \Swift_Mailer::newInstance($transport);
	    $mailer->send($message);
	    return $app->redirect($app['request']->getBaseUrl() .'/complete');
	}

メール送信後はcomplete画面にリダイレクト処理しています。これもSilexに用意された機能です。

Not Foundページ

ルーティングに基づいて動きますので、設定されたルーティングがないと例外を投げてくれます。
なので、アプリケーション実行時(run)をtry-catchしておけばOKです。本当はもう少し丁寧に処理したほうが良いかと思います。

<?php
...
// run app
try {
	$app->run();
}catch (Exception $e) {
	echo $e->getMessage();
}
感想

このindex.phpは全部で65行になりました。バリデーションの処理やメール送信の処理も書きつつルーティング機能、Twigのテンプレートが使えて65行はかなーり短いです。それほどページ数が多くない、またはTwigを手軽に使ってみたいという場合にはSilexはかなり使えると思います。

もちろん、エクステンションの仕組みがあるので、必要があれば機能を追加していくことも難しくないですし。

また、とにかくpharアーカイブされているのでFTPでアップロードするとしてもストレスは少ないです。

そして、Symfonyコンポーネントを使っているという点は安心感が大きいです。なにせSymfony2でも使っているコア機能です。

ちょっと触っただけですがSilexに惚れました。