初めてのLaravel 5.6 : (16) Formの作成

今回はフォームを作成して、記事をデータベースに登録します。


laravelcollective/html パッケージの追加

Laravel の View で Form を記述するには直接 HTML を記述する方法とヘルパー関数を使う方法があります。この記事では、パッケージのインストールを体験しておきたいので、laravelcollective/html パッケージをインストールして、ヘルパー関数を使用してみます。

このパッケージは、Laravel 4 では標準で組み込まれていたのですが、Laravel 5 からは別パッケージになり、ユーザーコミュニティーによってメンテナンスされるようになりました。
http://laravelcollective.com/

composer でインストール

composer コマンドでパッケージをインストールします。

composer require "laravelcollective/html":"^5.4.0"

composer のパッケージは vendor 以下にインストールされます。

vendor
└── laravelcollective
    └── html

Laravel へパッケージを組み込む( ver 5.4 以下でのみ )

config/app.php を編集して、 laravelcollective/html を Laravel に組込みます。
なお、この app.php の編集は Laravel バージョン 5.5 以降からは不要になりました。サービスプロバイダやファサードを Laravel が自動的に発見してくれます。

<?php
// config/app.php

return [
    // サービス・プロバイダーの登録
    'providers' => [

        // ...

        Collective\Html\HtmlServiceProvider::class,  // 追加
    ],

    // ファサードの登録
    'aliases' => [

        // ...

        'Form' => Collective\Html\FormFacade::class,  // 追加
        'Html' => Collective\Html\HtmlFacade::class,  // 追加
    ],
];

この辺の設定や仕組みは Laravel ではサービスプロバイダーとか、ファサードと言うのですが、ここでは詳細な説明は避けます。Laravel にパッケージを追加するには config/app.php に上記の設定の追加が必要ということだけ覚えて先に進みたいと思います。どうしても、気になる方はググってみて下さい。

Laravel 自体もサービスプロバイダーやファサードを使って各種機能を追加しています。connfig/app.php にはデフォルトで入っている物を確認できるので、見てみてください。


新規記事入力フォームの表示

Routing

ルートを追加します。

<?php
// routes/web.php

// ...
Route::get('articles', 'ArticlesController@index');

// 追加
Route::get('articles/create', 'ArticlesController@create'); // ①

Route::get('articles/{id}', 'ArticlesController@show'); // (a)
// ...

ArticlesController@createへのルートを追加しました。
注意点として、上記の①は (a) より先に追加します。そうしないと、”articles/create” にGETリクエストが来た時に、”articles/{id}”の方が先にマッチしてしまい、ArticlesController@show が実行されてしまいます。ルートは記述した順に、上からマッチングされます。

Controller

ArticlesController.php に createメソッドを実装します。
中身はビューを表示しているだけです。

<?php
namespace App\Http\Controllers;

// ...

class ArticlesController extends Controller
{

    // ...

    public function create()
    {
        return view('articles.create');
    }
}

View

create.blade.php を新規に作成します。

// resources/views/articles/create.blade.php

@extends('layout')

@section('content')
    <h1>Write a New Article</h1>

    <hr/>

    {!! Form::open() !!}
        <div class="form-group">
            {!! Form::label('title', 'Title:') !!}
            {!! Form::text('title', null, ['class' => 'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('body', 'Body:') !!}
            {!! Form::textarea('body', null, ['class' => 'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('published_at', 'Publish On:') !!}
            {!! Form::input('date', 'published_at', date('Y-m-d'), ['class' => 'form-control']) !!}
        </div>    
        <div class="form-group">
            {!! Form::submit('Add Article', ['class' => 'btn btn-primary form-control']) !!}
        </div>
    {!! Form::close() !!}
@endsection

新しい表記が出てきました。

1つ目は {!! XXXX !!} という表記です。これは blade テンプレートの表記で、PHP で評価した結果を表示する時に使用します。以前に同じような表記で、{{ XXXX }}を使いましたが、{!! XXXX !!} の方はエスケープ処理を行われず、{{ XXXX }} の方はエスケープ処理を行うという違いがあります。今回は Form の HTML を PHP で生成しているので、エスケープ処理を行わないように、{!! XXXX !!} を使っています。

2つ目は Form::XXXX という表記です。この部分で最初に追加インストールした laravelcollective/html パッケージ使用しています。一見すると Form クラスの static メソッドをコールしているように見えますが、実は Collective\Html\FormBuilder クラスのインスタンスメソッドをコールしています。ここでは Form クラスという alias (別名) を使って、簡素に Collective\Html\FormBuilder のインスタンスにアクセスしているという理解でよいかと思います。この仕組みをファサードといいます。ここではファサードの詳細は割愛して進めます。

ここでは Form ファサードの以下のメソッドを使っています。

  • Form::open() formの開始タグを生成
  • Form::close() formの終了タグを生成
  • Form::label() labelタグを生成
  • Form::input() inputタグを生成
  • Form::text() input[type=text]タグを生成
  • Form::textarea() textareaタグを生成
  • Form::submit() input[type=submit]タグを生成

詳細は下記のAPIリファレンスで確認することができます。
https://laravelcollective.com/docs/master/html

それから、class=”xxxx”の指定は、前回導入した Bootstrap の classを指定しています。これを指定するとフォームの表示がかっこ良くなります。

ここで、生成された Form の HTML を確認してみます。
http://localhost:8000/articles/create にアクセスし、ブラウザの機能でページのソースを表示します。

<form method="POST" action="http://localhost:8000/articles" accept-charset="UTF-8">
    <input name="_token" type="hidden" value="D1zCIbRcEzYrqracQtyPbDFuKcGHLHcm2aNddRig">
    <div class="form-group">
        <label for="title">Title:</label>
        <input class="form-control" name="title" type="text" id="title">
    </div>
    <div class="form-group">
        <label for="body">Body:</label>
        <textarea class="form-control" name="body" cols="50" rows="10" id="body"></textarea>
    </div>
    <div class="form-group">
        <label for="published_at">Publish On:</label>
        <input class="form-control" name="published_at" type="date" value="2018-08-27" id="published_at">
    </div>    
    <div class="form-group">
        <input class="btn btn-primary form-control" type="submit" value="Add Article">
    </div>
</form>

method には POST が設定されました。
action にはカレント URL (/articles/create) が設定されています。これは後で変更します。
<input name=”_token” type=”hidden” value=”…”> が自動的に挿入されました。これは CSRF 対策の為にトークンが自動で挿入されるようになっています。

laravelcollective/html パッケージを使わず、手動で form タグを記述する場合は CSRF トークンを自分で挿入する必要があります。その場合は Blade テンプレートの @csrf ディレクティグを使用します。

<form method="POST" action="/profile">
    @csrf
    ...
</form>

CSRF 対策に関しては、こちらを参照してください。
https://laravel.com/docs/5.7/csrf


新規記事の保存

Form が Submit された時の、DBへの保存処理を実装します。

Routing

ルートを追加します。

<?php
// app/Http/routes.php

// ...
Route::get('articles', 'ArticlesController@index');
Route::get('articles/create', 'ArticlesController@create');
Route::get('articles/{id}', 'ArticlesController@show');
// 追加
Route::post('articles', 'ArticlesController@store');

// ...

ArticlesController@store へのルートを追加しました。
get ではなく post を追加しています。

今まで、言われるがままに使っていた Route::XXX ですが、実はこれもファサードです。実態は Illuminate\Routing\Router です。Laravel 標準搭載のファサードなので、はじめから config/app.php に定義されています。

Controller

ArticlesController.php に store メソッドを実装します。

<?php
namespace App\Http\Controllers;

use App\Article;
use App\Http\ControllersController;

class ArticlesController extends Controller {

    // ...

    public function create() {
        return view('articles.create');
    }

    public function store() {
        // ① フォームの入力値を取得
        $inputs = \Request::all();

        // ② デバッグ: $inputs の内容確認
        dd($inputs);
    }
}

\Request::all() でフォームの入力値を全て取得しています。
この Request クラスもファサードで、実態は Illuminate\Http\Request です。
コントローラの名前空間でファサードクラスを使うには、グローバルクラスとして指定する必要があるので、クラス名の前に \(バックスラッシュ)を付けるのをお忘れなく。
Request の詳細は公式サイトのドキュメントを参照ください。

https://laravel.com/docs/5.6/requests
https://laravel.com/api/5.6/Illuminate/Http/Request.html

② この部分は一旦、フォームで入力された値を確認するのみとします。
dd() は引数で受け取った値を画面に表示するデッバッグ用の便利な関数です。

View

先ほど作成した、create.blade.php を修正します。

// resources/views/articles/create.blade.php

// ...

{!! Form::open(['url' => 'articles']) !!}

// ...

Form::open() に 引数を渡し、urlを指定しました。

生成されたFormのHTMLを確認してみます。

<form method="POST" action="http://localhost:8000/articles" accept-charset="UTF-8">
  ...

action の url が変更されました。これで submit ボタンを押した時に、ArticlesController@store が実行されます。

リクエスト内容の確認

ここで、 http://localhost:8000/articles/create にアクセスして記事を追加してみましょう。

画面に以下のように表示されれば成功です。

array:4 [▼
  "_token" => "D1zCIbRcEzYrqracQtyPbDFuKcGHLHcm2aNddRig"
  "title" => "入力したタイトル"
  "body" => "入力した記事"
  "published_at" => "2018-08-27"
]

dd() の表示で Form から送られてきたリクエストの内容が確認できました。
この dd() は変数の内容を表示してそこで処理を中止します。

DB への記事の保存

ArticlesController.php の store メソッドを完成させます。

class ArticlesController extends Controller {

    // ...

    public function store() {
        $inputs = \Request::all();

        // dd($inputs); 削除

        // ① マスアサインメントを使って、記事をDBに作成
        Article::create($inputs);

        // ② 記事一覧へリダイレクト
        return redirect('articles');
    }
}

① 以前にやったマスアサインメント機能を使って、Articles テーブルにデータを作成しています。Article モデルで fillable 変数の設定をお忘れなく。

② いままでのコントローラメソッドでは、view() メソッドを使って、ビューを表示していましたが、ここでは redirect() を使って、記事一覧の URL へリダイレクトさせています。

動作確認

もう一度、 http://localhost:8000/articles/create にアクセスして記事を追加してみましょう。
記事登録後に一覧画面にリダイレクトされて、登録した記事のタイトルが表示されていれば成功です。


記事一覧への新規作成ボタン

最後に記事一覧に、記事の新規作成ボタンを表示しておきましょう。

// resources/views/articles/index.blade.php

@extends('layout')

@section('content')
    <h1>
        Articles
        <!-- 追加 -->
        <a href="articles/create" class="btn btn-primary float-right">新規作成</a>
    </h1>

    <hr/>

    @foreach($articles as $article)
        ...
        ...
    @endforeach
@endsection

‘articles/create’ へのリンクタグを追加します。
bootstrap の class を “btn btn-primary” と設定して、リンクタグをボタンの様に表示します。
“float-right” でボタンを右によせています。

入力チェックは、次回に…


まとめ

以下の事が出来るようになりました

  • laravelcollective/html パッケージの追加
  • Form の作成と表示
  • Request::all() でフォームの入力値取得
  • ファサードの使い方
  • View -> Controller -> Model の流れでの DBへのデータ保存

コメントを残す