25
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SymfonyAdvent Calendar 2016

Day 12

Form Collection とイベント周りについて

Last updated at Posted at 2016-12-11

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種類が定義されており、いい感じに利用することで、いい感じになります

ということで、実装例を書いてみます。
内容は

スクリーンショット 2016-12-11 21.55.58.png

画像のようなフォームを用意

  • 演算子 [+ - * /]
  • 値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;
    }
}

成功

スクリーンショット 2016-12-11 21.56.29.png

失敗

スクリーンショット 2016-12-11 21.56.40.png

あまりいい例が思いつかず、とても無理矢理なイベントの使い方をしているのは反省。

まとめ

今回のコードからは読み取りにくいとは思いますが、各イベントを上手く利用することで、面倒な作業をSymfonyに任せるようなことができ、すごく楽ができます。

もうすこし複雑なパターンでいくと、
PRE_SUBMIT時に特定のリクエストデータが来た場合Formのフィールドをdisabledにすることにより、値の更新を不可にしたりなど…

けっこうぐだぐだな感じでしたがこれで終わりにします。ありがとうございました

25
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?