Symfony Advent Calendar 2016 12日目の記事。
Form Collection とイベント周りについて
Formイベント周りについて
Collectionについても書こうと思っていましたが、間に合わず断念m(_ _)m
というわけで、Formのイベント周りについてまとめてみました。
SymfonyのFormにはイベントというものが用意されており、
各イベントで、Formのモデルを成形したり、Formを動的に追加・編集・削除したり、ユーザーからのリクエストデータを成形したりなどを行なうことが可能です。
イベント一覧
Event | Formの操作 | Data | 得意なこと |
---|---|---|---|
PRE_SET_DATA | ◯ | Model | モデルの成形 |
POST_SET_DATA | ◯ | Model | Formの成形 |
PRE_SUBMIT | ◯ | Request Data | リクエストデータの成形。Formの成形 |
SUBMIT | ○ | Model | データ検証。Formの成形 |
POST_SUBMIT | ☓ | Model | 検証後の操作 |
追記 SUBMITの時もFormをaddなどして変更することは可能でした
イベントは上記のような5種類が定義されており、いい感じに利用することで、いい感じになります
ということで、実装例を書いてみます。
内容は
画像のようなフォームを用意
- 演算子 [+ - * /]
- 値1
- 値2
- 値3
各イベントでやること
- PRE_SET_DATA
- 値123の初期値を設定
- POST_SET_DATA
- 送信ボタンを追加
- PRE_SUBMIT
- 入力された値3を半分の数値にする
- SUBMIT
- 値123が数値であることを確認
- 値123で計算し、エラーが出ないことを確認
- POST_SUBMIT
- Formを検証し、エラーが無ければ、計算結果を出力
- Formを検証し、エラーであればFailedを出力
class SampleType extends AbstractType
{
// 演算の一覧
const OPERATION_PLUS = 1;
const OPERATION_MINUS = 2;
const OPERATION_MULTIPLIED = 3;
const OPERATION_DIVIDED = 4;
const operations = [
self::OPERATION_PLUS => '+',
self::OPERATION_MINUS => '-',
self::OPERATION_MULTIPLIED => '*',
self::OPERATION_DIVIDED => '/',
];
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('operation', ChoiceType::class, [
'label' => '演算子',
'choices' => self::operations
])
->add('num1', TextType::class, [
'label' => '値1'
])
->add('num2', TextType::class, [
'label' => '値2'
])
->add('num3', TextType::class, [
'label' => '値3 (強制的に半分)'
])
;
// モデルが触れる
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
// 初期値を作成
$data = $event->getData();
$data['num1'] = 10;
$data['num2'] = 20;
$data['num3'] = 30;
$data['operation'] = self::operations[1];
$event->setData($data);
});
// モデルとフォームが触れる フォームを追加、編集を行う場合はここ
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
// サブミットボタンを追加
$form = $event->getForm();
$form->add('submit', SubmitType::class);
});
// リクエストデータとフォームがさわれる。 リクエストデータの成形や、リクエストデータによってフォームを変更させることができる
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
// num3というリクエストデータを半分にする
$data = $event->getData();
$num3 = $data['num3'];
if (is_numeric($num3) and $num3 > 0) {
$data['num3'] = $num3 / 2;
}
$event->setData($data);
});
// リクエストデータがmappedされたモデルがさわれる。独自のValidation等で使う
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
// 検証
$form = $event->getForm();
// 入力された値が数字かどうか
if (!is_numeric($form->get('num1')->getData())) {
$form->get('num1')->addError(new FormError('num1 not numeric!'));
}
if (!is_numeric($form->get('num2')->getData())) {
$form->get('num2')->addError(new FormError('num2 not numeric!'));
}
if (!is_numeric($form->get('num3')->getData())) {
$form->get('num3')->addError(new FormError('num3 not numeric!'));
}
// 計算に失敗するかどうか
try {
$this->calc($event->getData());
} catch (ContextErrorException $e) {
$form->addError(new FormError($e->getMessage()));
}
});
// モデルが触れる。Validationを通った後にモデルを成形などに使える
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
// 成功可否を確認し、成功であれば集計値を。 失敗であればFailedを出力
$form = $event->getForm();
$data = $event->getData();
if ($form->isValid()) {
print('SUCCESS Total: ' . $this->calc($data));
} else {
print('Failed');
}
});
}
/**
* データを元に計算
*/
private function calc($data)
{
$sum = false;
switch ($data['operation']) {
case self::OPERATION_PLUS:
$sum = $data['num1'] + $data['num2'] + $data['num3'];
break;
case self::OPERATION_MINUS:
$sum = $data['num1'] - $data['num2'] - $data['num3'];
break;
case self::OPERATION_MULTIPLIED:
$sum = $data['num1'] * $data['num2'] * $data['num3'];
break;
case self::OPERATION_DIVIDED:
$sum = $data['num1'] / $data['num2'] / $data['num3'];
break;
}
return $sum;
}
}
成功
失敗
あまりいい例が思いつかず、とても無理矢理なイベントの使い方をしているのは反省。
まとめ
今回のコードからは読み取りにくいとは思いますが、各イベントを上手く利用することで、面倒な作業をSymfonyに任せるようなことができ、すごく楽ができます。
もうすこし複雑なパターンでいくと、
PRE_SUBMIT時に特定のリクエストデータが来た場合Formのフィールドをdisabledにすることにより、値の更新を不可にしたりなど…
けっこうぐだぐだな感じでしたがこれで終わりにします。ありがとうございました