BEAR.Sundayでコンタクトフォームを作ってみる④
BEAR.Sundayでコンタクトフォームを作ってみる③の続きです。
CSRF対策を入れたいところですが、その前にBEAR.Sunday(正確にはBEAR.Package)に含まれているAura Formを使うようにフォームをリファクタリングします。フォームの処理にAuraのライブラリ(Aura.Inputなど)を使うということです。
Aura FormにはCSRF対策も含まれているため、同時に目的も達成できます。
インターセプターの作成
まず、インターセプターを作成し、そこでフォームとそのバリデーションを定義します。
src/Interceptor/Contact/Form.php
<?php
/**
* Kenjis.Contact
*
* @author Kenji Suzuki <https://github.com/kenjis>
* @license MIT License
* @copyright 2014 Kenji Suzuki
* @link https://github.com/kenjis/Kenjis.Contact
*/
namespace Kenjis\Contact\Interceptor\Contact;
use Ray\Aop\MethodInterceptor;
use Aura\Input\FilterInterface;
use BEAR\Package\Module\Form\AuraForm\AuraFormTrait;
/**
* Aura.Input form
*
* @see https://github.com/auraphp/Aura.Input
*/
class Form implements MethodInterceptor
{
use AuraFormTrait;
/**
* Set form
*
* @param FilterInterface $filter
*/
private function setForm(FilterInterface &$filter)
{
$this->form
->setField('name')
->setAttribs(
[
'class' => 'form-control',
'id' => 'name',
'name' => 'name',
'size' => 20,
'maxlength' => 50
]
);
$filter->setRule(
'name',
'Enter your name (max 50 letters).',
function ($value) {
if (mb_strlen($value) == 0) return false;
if (mb_strlen($value) > 50) return false;
return true;
}
);
$this->form
->setField('email')
->setAttribs(
[
'class' => 'form-control',
'id' => 'email',
'name' => 'email',
'size' => 20,
'maxlength' => 100,
]
);
$filter->setRule(
'email',
'Enter your email adrress (max 100 letters).',
function ($value) {
if (mb_strlen($value) > 100) return false;
return filter_var($value, FILTER_VALIDATE_EMAIL);
}
);
$this->form
->setField('comment', 'textarea')
->setAttribs(
[
'class' => 'form-control',
'id' => 'comment',
'name' => 'comment',
'cols' => 40,
'rows' => 5,
]
);
$filter->setRule(
'comment',
'Enter comment (max 400 letters).',
function ($value) {
if (mb_strlen($value) == 0) return false;
if (mb_strlen($value) > 400) return false;
return true;
}
);
}
}
テンプレートの変更
フォームのテンプレートをform()関数を使うように変更します。
form()関数はAuraのヘルパーをTwigの関数化したものです。引数にフォーム生成のための配列を受け取り、inputタグなどを生成します。
--- a/src/Resource/Page/Contact.twig
+++ b/src/Resource/Page/Contact.twig
@@ -19,21 +19,23 @@
<p>Thank you!</p>
{% else %}
<form role="form" action="" method="post" enctype="multipart/form-data">
+ {{ form(form['__csrf_token']['hint']) }}
+
<div class="form-group{%if form['name']['error'] %} has-error{% endif %}">
<label class="control-label" for="name">Name</label>
- <input id="name" type="text" name="name" class="form-control" size="20" maxlength="50" value="{{ form['name']['value'] }}" />
+ {{ form(form['name']['hint']) }}
<label class="control-label" for="name">{{ form['name']['error'] }}</label>
</div>
<div class="form-group{%if form['email']['error'] %} has-error{% endif %}">
<label class="control-label" for="email">Email</label>
- <input id="email" type="text" name="email" class="form-control" size="20" maxlength="100" value="{{ form['email']['value'] }}" />
+ {{ form(form['email']['hint']) }}
<label class="control-label" for="email">{{ form['email']['error'] }}</label>
</div>
<div class="form-group{%if form['comment']['error'] %} has-error{% endif %}">
<label class="control-label" for="comment">Comment</label>
- <textarea id="comment" name="comment" class="form-control" cols="40" rows="5">{{ form['comment']['value'] }}</textarea>
+ {{ form(form['comment']['hint']) }}
<label class="control-label" for="comment">{{ form['comment']['error'] }}</label>
</div>
最初の{{ form(form['__csrf_token']['hint']) }}
はCSRF対策のためのトークンです。
ページコントローラの変更
ページコントローラContactを変更し、@BEAR\Sunday\Annotation\Form
アノテーションを追加します。
このアノテーションがあるメソッドを先ほどのContact\Formインターセプターがインターセプトして処理するようにするということです。
それから、バリデーションのロジックはインターセプターに移りますので削除します。
--- a/src/Resource/Page/Contact.php
+++ b/src/Resource/Page/Contact.php
@@ -25,17 +25,19 @@ class Contact extends ResourceObject
$this->mailer = $mailer;
}
+ /**
+ * @BEAR\Sunday\Annotation\Form
+ */
public function onGet()
{
return $this;
}
+ /**
+ * @BEAR\Sunday\Annotation\Form
+ */
public function onPost($name, $email, $comment)
{
- if (! $this->validation($name, $email, $comment)) {
- return $this;
- }
-
$this->sendmail($name, $email, $comment);
$this['code'] = $this->code = Code::CREATED;
@@ -46,34 +48,6 @@ class Contact extends ResourceObject
return $this;
}
- private function validation($name, $email, $comment)
- {
- $pass = true;
-
- if ((mb_strlen($name) == 0) || (mb_strlen($name) > 50)) {
- $this->body['form']['name']['error'] = 'Enter your name (max 50 letters).';
- $pass = false;
- }
-
- if ((mb_strlen($email) > 100) || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
- $this->body['form']['email']['error'] = 'Enter your email adrress (max 100 letters).';
- $pass = false;
- }
-
- if ((mb_strlen($comment) == 0) || (mb_strlen($comment) > 400)) {
- $this->body['form']['comment']['error'] = 'Enter comment (max 400 letters).';
- $pass = false;
- }
-
- if (! $pass) {
- $this->body['form']['name']['value'] = $name;
- $this->body['form']['email']['value'] = $email;
- $this->body['form']['comment']['value'] = $comment;
- }
-
- return $pass;
- }
-
private function sendmail($name, $email, $comment)
{
$data = [
インターセプターの設定
AppModule.phpで、Aura Formに必要なAura.Sessionモジュールのインストール、および、インターセプターのバインドを定義します。
--- a/src/Module/AppModule.php
+++ b/src/Module/AppModule.php
@@ -6,6 +6,7 @@ use BEAR\Package\Module\Package\StandardPackageModule;
use Ray\Di\AbstractModule;
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
+use BEAR\Package\Module\Session\AuraSession\SessionModule;
class AppModule extends AbstractModule
{
@@ -35,6 +36,16 @@ class AppModule extends AbstractModule
// override twig module
$this->install(new Provider\TwigModule($this));
+
+ // install aura.session
+ $this->install(new SessionModule());
+
+ // aspect @Form annotaion
+ $this->bindInterceptor(
+ $this->matcher->subclassesOf('Kenjis\Contact\Resource\Page\Contact'),
+ $this->matcher->annotatedWith('BEAR\Sunday\Annotation\Form'),
+ [$this->requestInjection('Kenjis\Contact\Interceptor\Contact\Form')]
+ );
// override module
// $this->install(new SmartyModule($this));
これで、Resource\Page\Contactクラス(とそのサブクラス)のメソッドに@BEAR\Sunday\Annotation\Form
というアノテーションがあると、Contact\Formインターセプターが処理をインターセプトするようになります。
これは、メソッドインターセプターによるアスペクト指向プログラミング(AOP)ということになります。メソッドインターセプターとは、メソッドの実行を横取り(intercept)して代理実行するものです。
メソッドインターセプターによる処理の解説
Resource\Page\ContactクラスのonGet()およびonPost()メソッドに@BEAR\Sunday\Annotation\Form
アノテーションがありますので、これらのメソッドの実行はインターセプトされ、Interceptor\Contact\Formインターセプターで処理されます。
ただし、このFormインターセプターにはフォームの定義とバリデーションしかありません。実際の処理は、BEAR\Package\Module\Form\AuraForm\AuraFormTraitにまとめられています。
実際には、このAuraFormTraitのinvoke()メソッドが実行されます。
vendor/bear/package/src/Module/Form/AuraForm/AuraFormTrait.php
public function invoke(MethodInvocation $invocation)
{
list($args, $hasSubmit) = $this->getSubmit();
$page = $invocation->getThis();
$this->setForm($this->filter);
$hasSubmit && $this->form->fill($args);
if ($this->form->filter()) {
// action
return $invocation->proceed();
}
// set hint and error message
foreach ($this->form->getIterator() as $name => $value) {
$errors = $this->form->getMessages($name);
$error = ($hasSubmit && $errors) ? $this->getErrorMessage($this->form->getMessages($name)) : '';
$page->body['form'][$name]['error'] = $error;
$page->body['form'][$name]['hint'] = $this->form->get($name);
}
return $page->onGet();
}
フォームをセット($this->setForm($this->filter)
)して、バリデーションを実行($this->form->filter()
)し、検証をパスしたら元のメソッドを実行($invocation->proceed()
)しています。
検証をパスしなかった場合は、エラーメッセージをセットしてページリソースのonGet()メソッドを実行して返しています。つまり、フォームが表示されるというわけです。
これで、CSRF対策もできました。今日はここまでにします。
BEAR.Sundayでコンタクトフォームを作ってみる⑤へ続く。
過去記事
- BEAR.Sundayでコンタクトフォームを作ってみる① BEAR.SundayのインストールとSwiftMailerによるメール送信
- BEAR.Sundayでコンタクトフォームを作ってみる② フォームの作成
- BEAR.Sundayでコンタクトフォームを作ってみる③ Twigのautoescapeと文字化けの修正
- BEAR.Sundayでコンタクトフォームを作ってみる④ Aura Formの利用とAOP ←今ここ
関連
Date: 2014/08/14