LaravelでFormを作るために「FormBuilder」を触ってみる（kristijanhusak/laravel-form-builderの紹介）

本記事は [Laravel Advent Calender 2017](https://qiita.com/advent-calendar/2017/laravel) 18日目の記事です。
初参加でちょっとドキドキしています……。


# 何がしたい？

* Formを書くときに、もう```<input type="radio">```と書きたくない。
* フロントサイドバリデーションも、サーバーサイドと一緒にやってくれませんか？
* データ型（Model）を書いたら、バリデーションもフォーム作りも全部自動でやってほしい。

そんな人のための、というよりそんな人の１人である僕が、Form Builderと出会って明るい未来を思い描けた（到達はしていない）、というお話です。

## 長い前置きの長い前置き

いきなりですが、LaravelのFormって使いにくくないですか？ というよりも、LaravelのFormデータのやり取り＝「ControllerでPOSTデータを受け取ってまるごとバリデーションに通してまるごとFillする一連の流れ」があまりにエレガントなのに対し、Formはほとんど、**HTMLをスクラッチで書いている**ようなイメージ……。もっとこう、エレガントな書き方が無いものか？と。

それを探るための試験的な記事です。
たまたま見かけたForm Builderと言うものを使うとうまくいくらしい、といった立ち位置から試しに使ってみた記事なので、もっとこうしたほうが良いよ！ こうするのがアタリマエだぜそんなことも知らんのか！ とかあればお気軽にコメントください。


## 標準のForm

Larvel5からFormはコアモジュールから削除されていて、LaravelCollectiveというサードパーティプロジェクトに移管されています。インストール手順などは下記に詳しく書いてあります。

[初めてのLARAVEL 5.1 : (16) FORMの作成](https://laravel10.wordpress.com/2015/03/08/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AElaravel-5-16-form%E3%81%AE%E4%BD%9C%E6%88%90/)

これを利用するとFormを少し機能的に、シンプルに書けるようになります。

```html+php:sample.blade.php
{!! Form::open(array('url' => 'foo/bar')) !!}
{!! Form::token() !!}
{!! Form::text('email', 'example@gmail.com') !!}
{!! Form::password('password') !!}
{!! Form::submit('Click Me!') !!}
{!! Form::close() !!}
```

が

> Larvel5からFormはコアモジュールから削除されていて

これ、さらっと流しましたが、つまりLaravelには**標準でFormづくりを助けてくれる機能が無い**ってことですよね！ 切り離すのが惜しいくらい親和性があって便利なモジュールがないということですよね！？


# 個人的に理想のFormの書き方

## 僕はHTMLが書きたいんじゃない！ データ入力フォームが書きたいんだ！！

ちょっと話が飛びますが、たとえば、いくつかの選択肢から1つを選ぶというフォームを書く場合、SELECTやRADIOを使うことになりますが、HTMLだと

```html
<select class="" name="option1">
  <option value="0">---選択されていません--</option>
  <option value="1" selected>選択肢１</option>
  <option value="2">選択肢２</option>
  <option value="3">選択肢３</option>
</select>
```

だったり

```html
<fieldset>
  <label><input type="radio" name="option1" value="1" checked="checked">選択肢１</label>
  <label><input type="radio" name="option1" value="2">選択肢２</label>
  <label><input type="radio" name="option1" value="3">選択肢３</label>
</fieldset>
```

と書きます。
select の中の選択肢タグはなんだっけ？ value と name はドコに書くんだっけ？ 選択済みは selected だっけ checked だっけ？ radioタグは無くてinputタグのtype="radio"か？ labelは外か隣かどっちに書くんだっけ？……といった、目的とは違うことをいろいろ考える必要があります。

LaravelCollectiveのFormも結構便利になっていて、SELECTは

```php
echo Form::select('option1', array(
    '1' => '選択肢１', 
    '2' => '選択肢２',
    '3' => '選択肢３',
  ), '1');
```

とかなりスッキリしますが、RADIOは

```html+php
<fieldset>
  <label><?php echo Form::radio('option1', '1', true); ?>選択肢１</label>
  <label><?php echo Form::radio('option1', '2'); ?>選択肢２</label>
  <label><?php echo Form::radio('option1', '3'); ?>選択肢３</label>
</fieldset>
```

といったように、まだHTML風です。なんていうか、あくまでLaravelCollectiveの機能は**HTMLのマクロ**といったスタンスなんですよね。~~これだったらHTMLをそのまま書いたほうが早いような。~~

そもそも、このHTMLのRADIOの仕様がイマイチです。RADIOを単独で使うことはまず無いので、nameのoption1を3回書くのが不自然だし、できればLabelも自動的に書き出してほしい。それよりも、**「選択肢から選ぶ」という行為はSELECTと同じなので、書き方も同じ**であってほしい。つまり、こう。

```php
echo Form::radio('option1', array(
    '1' => '選択肢１', 
    '2' => '選択肢２',
    '3' => '選択肢３',
  ), '1');
```

もう少し言うと、入力するデータをチェックするバリデーションも必要です。例えば、こう。

```php
echo Form::radio('option1', array(
    '1' => '選択肢１', 
    '2' => '選択肢２',
    '3' => '選択肢３',
  ), null, array( 'validation_rules'=>'required' ) );
```

# kristijanhusak/laravel-form-builder

前置きが長くなりましたが、そんなフォーム作りを実現してくれるのが **Form Builder** というもので（他のフレームワークにもあったり、単独でライブラリとして配布されていたりする一般名詞）、Laravel用だと **kristijanhusak/laravel-form-builder** がGITHUBでのスターも多く、広く受け入れられているのかなぁという印象。早速使ってみます。

**kristijanhusak/laravel-form-builder**
https://github.com/kristijanhusak/laravel-form-builder

## インストール

公式の[README](https://github.com/kristijanhusak/laravel-form-builder)に従っていきます。（が、以下はLaravel5.5のまっさらなプロジェクトからスタートしているので公式と若干異なります。）

```
composer require kris/laravel-form-builder
```

つぎに ```config/app.php``` を開いて、以下を追加。

```php:config/app.php
    'providers' => [
        // ...
        Kris\LaravelFormBuilder\FormBuilderServiceProvider::class
    ]
```

これは、後ほどコントローラメソッドに ```public function create(FormBuilder $formBuilder)``` と書いたときに、Laravelから $formBuilder のインスタンスを受け取るための設定（DI：依存性注入）。続いて同じファイルに、

```php:config/app.php
    'aliases' => [
        // ...
        'FormBuilder' => Kris\LaravelFormBuilder\Facades\FormBuilder::class
    ]
```

これは、おもむろに```FormBuilder::xxxx()```と静的呼び出しをしたときに実際に実行されるクラスの指定で、先程の、Laravelにインスタンスを作ってもらう時に必要な設定（たぶん……）。

## フォームクラスの作成

インストールできたら、フォームを作ります。

```
php artisan make:form Forms/SongForm --fields="name:text, lyrics:textarea, publish:checkbox"
```

まずフォームをクラス化するというのが良い感じですし、それを artisan で実行できるというのがLaravelっぽくてますます良い感じ。このコマンドで、app/Formsというディレクトリに下記のファイルが生成されます。

```php:app/Forms/SongForm.php
<?php

namespace App\Forms;

use Kris\LaravelFormBuilder\Form;

class SongForm extends Form
{
    public function buildForm()
    {
        $this
            ->add('name', 'text')
            ->add('lyrics', 'textarea')
            ->add('publish', 'checkbox');
    }
}
```

そのままだとフィールド値しかないので、試しにSUBMITボタンを追加してみます。

```php:app/Forms/SongForm.php
            ->add('publish', 'checkbox') // ;を取って
            ->add('submit', 'submit', ['label' => '保存']); // 追加
```



## コントローラを準備

公式には書いてないけどコントローラを作るならこう。

```
php artisan make:controller SongsController
```

```php:app/Http/Controllers/SongsController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Kris\LaravelFormBuilder\FormBuilder; // 追記

class SongsController extends Controller
{
  // 以下を追記
  public function create(FormBuilder $formBuilder)
  {
      $form = $formBuilder->create(\App\Forms\SongForm::class, [
          'method' => 'POST',
          'url' => route('song.store')
      ]);

      return view('song.create', compact('form'));
  }

  public function store(FormBuilder $formBuilder)
  {
      $form = $formBuilder->create(\App\Forms\SongForm::class);

      if (!$form->isValid()) {
          return redirect()->back()->withErrors($form->getErrors())->withInput();
      }

      // 保存する処理はここ（今回は省略）
  }
}
```

## ルーティング

```php:routes/web.php

// 以下を追記
Route::get('songs/create', [
    'uses' => 'SongsController@create',
    'as' => 'song.create'
]);

Route::post('songs', [
    'uses' => 'SongsController@store',
    'as' => 'song.store'
]);

```

## VIEW

公式は ```@extends``` とありますが、親bladeをまだ作ってないので割愛し、代わりにBootstrapのスタイルシートを読み込んでみます。

```php:resources/views/song/create.blade.php
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
{!! form($form) !!}
```

## テスト

artisan でローカルサーバーを立ち上げて……

```
php artisan serve
```

ブラウザで ```http://127.0.0.1:8000/songs/create``` にアクセスします（もしドメインが異なれば適宜読み替えてください）。下図のようなフォームが表示されました。

![form1.jpg](https://qiita-image-store.s3.amazonaws.com/0/129854/f6eca122-7710-48c4-0156-e4fb2c39e1b4.jpeg)

生成されたHTMLはこのとおり

```html
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<form method="POST" action="http://127.0.0.1:8000/songs" accept-charset="UTF-8">
  <input name="_token" type="hidden" value="8Jr8nQ6kE7abrDu0jtrN7tuM7jDGtq9wTRHkgFAc">
  <div class="form-group">
    <label for="name" class="control-label">Name</label>
    <input class="form-control" name="name" type="text" id="name">
  </div>
  <div class="form-group">
    <label for="lyrics" class="control-label">Lyrics</label>
    <textarea class="form-control" name="lyrics" cols="50" rows="10" id="lyrics"></textarea>
  </div>
  <div class="form-group">
    <input id="publish" name="publish" type="checkbox" value="1">
    <label for="publish" class="control-label">Publish</label>
  </div>
  <button class="form-control" type="submit">保存</button>
</form>
```

チェックボックスにLabelが付与されてdivで囲ってくれているのがまさに理想的。
CSRFトークンが自動で付与されていたりもします。

form-control というクラスがデフォルトで付与されているので、Bootstrapを当てるとちょっとスタイリッシュになります。本気でスタイリングする場合は、下記のように class を変更したり、設定を変更したりするようです。

```php:app/Forms/SongForm.php
            ->add('publish', 'checkbox') // ;を取って
            ->add('submit', 'submit', ['label' => '保存','attr'=>['class'=>'btn btn-primary']])
```



# ひとまずここまで……

次のステップはバリデーションで、ここがスッキリ書けるとLaraverlのフォームまわりはだいぶスッキリするんじゃないかと思いますが、実際に自分のプロジェクトで組み込んでみて使い慣れてから書き進めたいと思います。

本当はさらにもっと踏み込んで、Modelクラス内に、

```php:MyModel.php
MyModel extends Model {

  protected $columns = [
    'id'    => [ 'label'=>'ＩＤ',   'type':'SERIAL', 'rule'=>'required' ],
    'name'  => [ 'label'=>'名前',   'type':'TEXT',   'rule'=>'required|max-length:50' ],
    'email' => [ 'label'=>'メール', 'type':'EMAIL',  'rule'=>'required|with-confirm' ],
    'job'   => [ 'label'=>'職業',   'type':'SELECT', 'rule'=>'required', 'option'=>['会社員','自営業',...] ],
  ];

}
```

と書いておいたら、フォームづくりも、フロントとサーバーのバリデーションも、CSVデータのエクスポートとインポートも、全部自動でできるようになったらいいなーと妄想しています。