25
19

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.

LaravelAdvent Calendar 2018

Day 17

laravel-form-builder のすすめ

Last updated at Posted at 2018-12-16

Laravel Advent Calendar 2018 - Qiita の17日目の記事です。

福岡のフリーランスエンジニア:relaxed:です
今年はLaravel触ることが多いのでLaravelのことを書きます
去年まではSymfonyの記事を書いていた気がする。

さいしょに

対象っぽい人

  • Laravelを使っている
  • フロントはbladeだ
  • Controller内にValidationを書きまくるのがなんかつらい
  • Bladeつらい

対象じゃないっぽい人

  • Laravelを使っていない
  • フロントはvueだ(vueだとちょっと微妙かもしんない

ざっくり

SymfonyからLaravelを触り始めた自分が感じた部分として、Formの機能が弱すぎると感じた
デフォルトのFormHelperの機能は弱く、使う利点がいまいちだった。
SymfonyもLaravelも同じMVCだと思うが
SymfonyはMVC+F(Model+View+Controller+Form) といった印象があった(MVC+Fという名前は今思いついた:angel:
それほどSymfonyのFormがもつ機能は優秀だったきがする。というか優秀。それだけでSymfonyを選ぶ理由になりうるくらいには。

詳しい事書き出すと面倒だから、Laravelを使う上で自分が納得するまでForm機能をLaravelに組み込んだ話をしていきます

FormBuilderとは

laravel-form-builder
:point_up:これ。こいつのおかげで健全なLaravelライフを送れている

ドキュメント

導入方

いつものこれ

composer require kris/laravel-form-builder

その後、app.php になんか追記

あとは公式見て:wink:
https://github.com/kristijanhusak/laravel-form-builder

書き方

普段ControllerとBladeとModelという構成で画面を作っていくが、これにFormクラスが増える感じ
Modelは割愛するので雰囲気でおねがいします!

Formクラス

src/app/Forms/UserForm.php
namespace App\Forms;

use Kris\LaravelFormBuilder\Form;

class UserForm extends Form
{
    public function buildForm()
    {
        // 新規の時だけ、ユーザーIDを変更できる仕様てきな
        if ($this->isNew()) {
            $this->add('user_id', 'text', [
                'label' => 'ユーザーID',
                'rules' => 'required|alpha_num' // 英数半角
            ]);    
        }
        
        $this
            ->add('age', Field::TEXT, [
                'label' => '利用可能回数',
                'rules' => 'required|integer|min:18' //18禁てきな
            ])
            ->add('birthday', Field::TEXT, [
                'label' => '有効日数',
                'rules' => 'nullable|date_format:Y-m-d', // 見ての通り
            ])
            ->add('self_introduce', Field:: TEXTAREA, [
                'label' => '自己紹介',
                'rules' => 'max:255',
                'default_value' => 'こんにちは' // 値が入力されなかった場合、自動的に自己紹介が"こんにちは" になる(後述)
            ])
            ->add('only_view', Field::TEXT, [
                'label' => '見えるけど編集できないフォーム',
                'disabled' => true, //オプションで利用するdisabled(ControllerのgetFormValueで使う)
                'attr' => [
                    'disabled' => 'disabled' // こっちが表示のdisabled
                ]
            ])
            ->add('submit', Field:: BUTTON_SUBMIT', ['label' => '保存']);
    }

    // 新規かどうか。
    // モデルのidがあるかどうかでチェックしてる
    // 本来はFormクラスを継承してそこに定義しておくと、どこでも使えてよき
    protected function isNew()
    {
        return empty($this->model->id);
    }
}

形としては下記の感じ

$this->('モデルKey', Field::タイプ, [
   'label' => 'ラベル',
   'rules' => 'バリデーション'
]);
src/app/Http/Controller/UserController
<?php

namespace App\Http\Controllers;

use App\Forms\UserForm;
use App\Models\User;
use Illuminate\Http\Request;
use Kris\LaravelFormBuilder\Form;

class UserController extends Controller
{
    // 一覧画面
    public function index()
    {
        return view('user.index', [
            'paginate' => $paginate = User::paginate(15),
        ]);
    }
    
    // 新規作成のページ
    public function create()
    {
        return view('user.create', [
            'form' => $this->generateCreateForm()
        ]);
    }

    // 新規登録処理
    public function store(Request $request)
    {
        $form = $this->generateCreateForm();

        // formを検証して問題があればいつものあれ
        if (!$form->isValid()) {
            return redirect()->back()
                ->withInput($request->all())
                ->withErrors($form->getErrors());
        }

        // getFormValue 忖度して値を取得しCreate
        $entity = User::create($this->getFormValue($form));

        $entity->save();
        
        return redirect(route('user.edit', ['user' => $entity->id]));

    }

    public function edit($id)
    {
        $entity = User::findOrFail($id);
        
        return view('user.edit', [
            'form' => $this->generateEditForm($entity)
        ]);
    }

    public function update(Request $request, $id)
    {
        $entity = User::findOrFail($id);

        $form = $this->generateEditForm($entity);

        // formを検証して問題があればいつものあれ
        if (!$form->isValid()) {
            $this->flash_error_message();
            return redirect()->back()
                ->withInput($request->all())
                ->withErrors($form->getErrors());
        }
        
        // getFormValue 忖度して値を取得。Modelを更新
        $entity->fill($this->getFormValue($form));

        $entity->save();

        return redirect(route('user.edit', ['user' => $id]));
    }

    // 新規作成用のFormオブジェクトを返す
    protected function generateCreateForm()
    {
        return $this->generateForm(UserForm::class, [
            'method' => 'POST',
            'url' => route('user.store'),
        ]);
    }

    // 編集用のFormオブジェクトを返す
    protected function generateEditForm($entity)
    {
        return $this->generateForm(UserForm::class, [
            'method' => 'PUT',
            'url' => route('user.update', $entity->id),
            'model' => $entity,
        ]);
    }

    /**
     * 標準の機能だとFormの値の取得がイマイチなので独自でつくったやつ。
     * Requestで送られた値とFormで定義した項目で忖度して値を返す
     * 見やすいようにControllerに定義したが、実際はFormを継承したクラスとかにやるのが吉
     * 
     * disabled => true とされているものは無視する
     * @param Form $form
     * @param $with_null : Requestで値が送られてこなくても、model_key => null で値をかえす
     * @param $with_default : Requestで送られてきた値がnullなら、default_value に定義された値を返す
     * @return array
     */
    protected function getFormValue(Form $form, $with_null = true, $with_default = false)
    {
        $values = [];

        foreach($form->getFields() as $field) {
            // ネストした値 たぶん動く?
            if ($field instanceof Form) {
                $value = $this->getFormValue($field);
                $value[$field->getName()] = $value;
                continue;
            }

            // フォームタイプがsubmitは無視
            if ('submit' === $field->getType()) {
                continue;
            }

            // disabledがfalse以外はスキップ
            // disabledオプションは本来提供されていないが
            // UserFormの定義でdisabledがtrueになっている場合、Requestで値がきていても返さない
            if (false !== $field->getOption('disabled', false)) {
                continue;
            }
            
            $value = $field->getRawValue();

            // with_defaultが有効なら、nullの時にdefaultの値を取得する
            if (null == $value and true === $with_default) {
                $value = $field->getDefaultValue();
            }

            // with_nullがfalseで値がnullの場合は値をセットしない
            if (null == $value and false === $with_null) {
                continue;
            }

            $values[$field->getName()] = $value;
        }

        return $values;
    }
}

src/app/resources/views/user/createORedit.blade
// これで展開!!
{!! form($form) !!}

見てほしいのはControllerに普段書いてるであろうValidationの処理がなくなっているのと
Bladeは1行だけで済む所。ソースを適材適所な感じに書けるのでコード的にも精神的にもスッキリする

あとは独自で作った関数のgetFormValueだが、これが結構キモな感じ
値が入力されなかったときにFormに定義したdefault_valueを返したり
disabledのオプションがあった場合は、Requestに値があったとしてもgetFormValueのレスポンスに含めなくする。
これによって、不要なRequestを弾ける。(フィールドをdisabledにしているのに、検証モードで無理やり活性化して送信された時を想定

得られる利点ざっくり

  • Formの情報をバックエンドに定義できる
    • Bladeにモデルの状態によって色々と書き分けるのは微妙だったりするが、それが避けれる
    • Formクラス内でそのあたりを完結して書けるといいことがある(後述(↓下2つ))
  • Controllerに書いていたValidationの処理を別クラスに書ける
    • ControllerにValidationを書いた時に結構な行数をController内に書く必要があるが、それをFormにまとめて書けるのが良い。
    • Modelの状態でValidationの内容が変わる事があるがFormクラスに定義することで面倒くささを吸収してくれる
  • Bladeを殆ど触らなくなる
    • 基本的なレイアウトであればBladeに {!! form($form) !!} これするだけで終わる。
    • FormClassにどういう入力タイプかとかも定義するから!

対応しているインプットタイプ

http://kristijanhusak.github.io/laravel-form-builder/
https://github.com/kristijanhusak/laravel-form-builder/blob/master/src/Kris/LaravelFormBuilder/Field.php
面倒なのでここを見て!!色々できるよ!

基本的な事は大体できるし、selectのoptionとか別のModelを元に出したりできる
http://kristijanhusak.github.io/laravel-form-builder/field/entity.html

Entity的にRelationでネストしたような奴も恐らくいい感じにできるが、まだ試してない
http://kristijanhusak.github.io/laravel-form-builder/field/child-form.html

他にもすごいよFormBuilderさん!

  • 基本的にbootstrap3のレイアウトで出力されるが、そのへん好きに弄れる。
  • 独自のFormTypeが作れる 自分はdatetimepickerを簡単に作れるようにしたりしてる
  • ソース自体は結構簡単なので、手の届かない所は自分で拡張してカスタムしやすい

さいごに

  • やっぱ利点は全体的にスッキリすること
  • ちょっとしたModel依存でFormの状態を変える時も、大体Formクラスさえ弄るだけでよくなる
  • FormBuilderはデフォルトでもある程度の機能を提供してくれるが、カスタムした方が便利。実際はもうちょっとカスタムしてる。書くのが面倒なのでごめんなしあ
  • 例年通り記事に適当感あるのは許してください!流行りの抜け感とかのそれです!
  • 結構適当に書いてるので、質問があれば答えます:cry:
  • 12/24・25日がいまだになんの日かわからない:rolling_eyes:
25
19
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
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?