LaravelによるWebアプリケーション開発入門 第三回 Controller

## はじめに
第二回は[こちら](https://qiita.com/toro_ponz/items/76c5456c86af18720f41)
第三回はLaravelのControllerについて解説します。
今回のソースはこちらで確認できます。[GitHub](https://github.com/toro-ponz/laravel-messenger/pull/2)

### Route
[前回](https://qiita.com/toro_ponz/items/76c5456c86af18720f41)に引き続きルーティングファイルを見ていきます。

```php:routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    $now = \Carbon\Carbon::now();
    
    return view('welcome')
        ->with('now', $now);
});
```

前回はViewに変数の定義などを書くべきでないためルーティングファイルに記載しましたが、ここに書くのも好ましくありません。あくまでルーティング関連の記述のみにとどめるべきです。

ではどこに書くか、それこそが今回のメインであるControllerです。

## Controller
正直自分自身Controllerがなんなのかうまく言語化できていないんですが、ふわっと**受付の人**みたいなイメージを持っています。今日の日付などは把握していますが、ある部署のXXさんが今どこで何をしているかなどは、その部署に電話をしないと知りえない、といったように、**簡単なロジックを自分で、もしくは難しいロジックを問い合わせによって取得し、訪問者に結果を返す(Viewに変数を渡してhtmlとして返す)もの**、というように認識しています。ここで言う**難しいロジック**はModelが担うべきものですが、それについては第四回でやる予定なので、Modelを使わない簡単な実装について解説していきます。

Controllerは**app/Http/Controllers**以下に配置します。例のごとくViewに合わせて、**WelcomeController**を作成……と言いたい所ですが、トップページが`welcome`っていうのがあまりしっくりこないので、この機会に`index`に直しましょう。

### welcome => index

```diff:resources/views/index.blade.php
  /**
   * welcome.blade.phpからリネーム
   */

  @section('title', 'Laravel')

  @section('content')
-   <div class="v-welcome">
+   <div class="v-index">
      <div class="title m-b-md">
        Laravel
      </div>
```

```diff:resources/assets/sass/views/index.scss
  /**
   * welcome.scssからリネーム
   */

- .v-welcome{
+ .v-index {
    .title {
      font-size: 84px;
    }
    .m-b-md {
      margin-bottom: 30px;
    }
  }
```

```diff:resources/assets/sass/app.scss
  // views
  @import "views/layouts/frame";
- @import "views/welcome";
+ @import "views/index";
```

```diff:routes/web.php
Route::get('/', function () {
    $now = \Carbon\Carbon::now();
    
-   return view('welcome')
+   return view('index')
        ->with('now', $now);
});
```

```powershell:コンパイルも忘れずに
PS > npm run production
```

### Controllerを作成
Controllerは、**App/Http/Controller**クラスを継承して実装します。
手で書いても良いですが、`artisan`コマンドを使って空のコントローラーを生成できます。

```powershell
PS > php artisan make:controller IndexController
```

`app/Http/Controllers/IndexController.php` が生成されているので、ここにアクションを追加して行きます。

```php:app/Http/Controllers/IndexController.php
<?php

namespace App\Http\Controllers;

use Carbon\Carbon;

class IndexController extends Controller
{
    public function index()
    {
        $now = Carbon::now();

        return view('index')
            ->with('now', $now);
    }
}
```

```diff:routes/web.php
- Route::get('/', function () {
-     $now = \Carbon\Carbon::now();
-
-     return view('welcome')
-         ->with('now', $now);
- });
+ Route::get('/', 'IndexController@index')->name('index');
```

* `Route::get()`の第二引数を**コントローラークラス名@メソッド名**と指定することで指定URIにアクセス時にそのメソッドへ処理が回ってくるようになります
* `Route::get()`の第二引数は、**App\Http\Controllers**から探されるため、完全な名前空間を指定する必要はありません。
    * **App\Http\Controllers\Hoge\FugaController**を指定する場合は、`Route::get('/', 'Hoge\FugaController@index')`と書くことが出来ます。
* 後ろについている`->name('index');`は、そのルーティングの名前を指定するものです。
    * こうすることで、PHP上で`route('index')`が http://localhost:8000 と出力できるようになります
    * `<a href="{{ route('index') }}>トップページに戻る</a>`といった感じで良く使います

修正が完了したら、下記のコマンドで確認しましょう。

```powershell
PS > php artisan route:list
+--------+----------+----------+-------+--------------------------------------------+--------------+
| Domain | Method   | URI      | Name  | Action                                     | Middleware   |
+--------+----------+----------+-------+--------------------------------------------+--------------+
|        | GET|HEAD | /        | index | App\Http\Controllers\IndexController@index | web          |
|        | GET|HEAD | api/user |       | Closure                                    | api,auth:api |
+--------+----------+----------+-------+--------------------------------------------+--------------+
```

GET`/`にアクセスした際に、**App\Http\Controllers\IndexController@index**が呼びだされていることが分かりますね。

このままでも良いですが、TOPのコントローラーはGET`/`のアクションしか持たず、ほかのアクションが追加されることも考えづらいので**シングルアクションコントローラー**として実装してみましょう。

### シングルアクションコントローラー
わざわざこのような名前がついているということは、**通常はコントローラーは複数のアクションを持っている**ということになります。
**アクション**とは、**HTTPリクエストに対してレスポンスを返す処理**のことです。
また、アクションの処理本体の関数の事を**アクションメソッド**と呼びます。

```diff:app/Http/Controllers/IndexController.php
-   public function index()
+   public function __invoke()
    {
        $now = Carbon::now();

        return view('index')
            ->with('now', $now);
    }
```

```diff:routes/web.php
- Route::get('/', 'IndexController@index')->name('index')
+ Route::get('/', IndexController::class)->name('index');
```

シングルアクションコントローラーにするには、アクションメソッド名を**__invoke**にして、ルーティングでの指定をクラス指定にするだけです。

余談ですが、普段書くときクラス名は**シングルアクションコントローラーだと単数形**、**そうでない場合は複数形**にするようにしています。

### お問い合わせフォームを作る
特殊な例のみ紹介してもわかりづらいので、複数のアクションメソッドを持つコントローラーの例として**お問い合わせフォーム**を作ってみましょう。

#### 前準備
Formを実装するにあたって、**Formファザード**が便利なので、追加します。

```powershell:PowerShell
PS > composer require laravelcollective/html5.5.*
```

`config/app.php`の`aliases`に以下を追加します

```diff:config/app.php
    'aliases' => [
        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Auth' => Illuminate\Support\Facades\Auth::class,
        'Blade' => Illuminate\Support\Facades\Blade::class,
        'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
        'Bus' => Illuminate\Support\Facades\Bus::class,
        'Cache' => Illuminate\Support\Facades\Cache::class,
        'Config' => Illuminate\Support\Facades\Config::class,
        'Cookie' => Illuminate\Support\Facades\Cookie::class,
        'Crypt' => Illuminate\Support\Facades\Crypt::class,
        'DB' => Illuminate\Support\Facades\DB::class,
        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
        'Event' => Illuminate\Support\Facades\Event::class,
        'File' => Illuminate\Support\Facades\File::class,
+       'Form' => Collective\Html\FormFacade::class,
        'Gate' => Illuminate\Support\Facades\Gate::class,
        'Hash' => Illuminate\Support\Facades\Hash::class,
        'Lang' => Illuminate\Support\Facades\Lang::class,
        'Log' => Illuminate\Support\Facades\Log::class,
        'Mail' => Illuminate\Support\Facades\Mail::class,
```

ここに記載することで、プロジェクト上で名前空間を明示的に指定しなくて済むようになります。

#### 概要
* GET `contact` でお問い合わせフォームを表示
    * 名前とメールアドレス、問い合わせ内容を入力させる
* POST `contact` でお問い合わせフォーム送信
    * 入力されたデータをログに出力する
    * その後完了ページにリダイレクトし、お礼のメッセージを出す
* GET `contact/complate` で完了ページを表示

#### 実装
概要から、必要なビュー数と、コントローラーのアクション数はいくつでしょうか？

| HTTP | URI | ビュー | アクション | 処理内容 |
|---|---|---|---|---|
| GET | contact | 要 | 要 | フォームを表示 |
| POST | contact | 不要 | 要 | 送信されたフォーム内容を受け取りログに出力 |
| GET | contact/complate | 要 | 要 | フォーム送信後に表示 |

表のようにそれぞれ、2つ,3つとなります。

これを元に、実装していきます。

##### View
```html:resources/views/contact/index.blade.php
@extends('layouts.frame')

@section('title', 'お問い合わせ')

@section('content')
  <div class="v-contact-index">
    <h1>お問い合わせフォーム</h1>
    <div class="form-box">
      {!! Form::open(['url' => route('contact.send'), 'method' => 'post']) !!}
        <h2>名前</h2>
        {!! Form::text('name', null, ['required' => 'required', 'placeholder' => '山田 太郎']) !!}
        <h2>メールアドレス</h2>
        {!! Form::email('email', null, ['required' => 'required', 'placeholder' => 'example@hoge.jp']) !!}
        <h2>お問い合わせ内容</h2>
        {!! Form::textarea('content', null, ['required' => 'required', 'placeholder' => 'お問い合わせ内容']) !!}
        <div class="submit-button-box">
          {!! Form::submit('送信する') !!}
        </div>
      {!! Form::close() !!}
    </div>
  </div>
@endsection
```

* `Form::open()` ~ `Form::close()`でフォームを作成できます
    * url,method等指定できます
    * CSRF Tokenも同時に生成してくれます
* `Form::text`や`Form::email`などで、`input`要素を作成できます
    * 第一引数に`name`、第二引数に`value`を指定
* `Form::submit`で送信ボタンを作成できます

Formファザードの詳しい使い方は以下を参照してください。
[Laravel Collective](https://laravelcollective.com/docs/master/html)

```html:resources/views/contact/complate.blade.php
@extends('layouts.frame')

@section('title', 'お問い合わせ完了')

@section('content')
  <div class="v-contact-complate">
    <h1>お問い合わせありがとうございます。</h1>
    <p>
      <a href="{{ route('contact.index') }}">お問い合わせフォームに戻る</a>
    </p>
    <p>
      <a href="{{ route('index') }}">トップページに戻る</a>
    </p>
  </div>
@endsection
```

##### Controller

```php:app/Http/Controllers/ContactsController.php
<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Http\Request;
use Log;

class ContactsController extends Controller
{
    public function index()
    {
        return view('contact.index');
    }

    public function send(Request $request)
    {
        // 送信された内容を取得
        $contents = [
            '名前' => $request->input('name'),
            'メールアドレス' => $request->input('email'),
            '問い合わせ内容' => $request->input('content'),
        ];

        // ログに出力
        Log::notice($contents);

        // 完了ページにリダイレクト
        return redirect()->route('contact.complate');
    }

    public function complate()
    {
        return view('contact.complate');
    }
}
```

* リクエストデータは、アクションメソッドの引数として受け取ることが出来ます
* POSTなどのアクションメソッドでは、F5で再送信されるのを防ぐために、`return view()`とせずに、リダイレクトしましょう

##### Route
```diff:routes/web.php
  Route::get('/', IndexController::class)->name('index');
+
+ // お問い合わせ
+ Route::group(['prefix' => 'contact', 'as' => 'contact.'], function () {
+     Route::get('/', 'ContactsController@index')->name('index');
+     Route::post('/', 'ContactsController@send')->name('send');
+     Route::get('/complate', 'ContactsController@complate')->name('complate');
+ });
```

* Route::groupはその名の通りルーティングのグループ化をします
    * `prefix`は共通のURIを指定します
        * URIに前方結合され、`contact/`や`contact/complate`というURIになります
    * `as`はルートの名前を指定します
        * nameに前方結合され、`contact.index`や`contact.send`というルート名になります
* URIの先頭の`/`は付けなくても良いです(個人的に気に入って付けているだけなので)

つまり、以下のように書いても同様の結果が得られます。

```diff:routes/web.php
- Route::get('/', IndexController::class)->name('index');
+ Route::get('', IndexController::class)->name('index');
+
+ // お問い合わせ
+ Route::get('contact', 'ContactsController@index')->name('contact.index');
+ Route::post('contact', 'ContactsController@send')->name('contact.send');
+ Route::get('contact/complate', 'ContactsController@complate')->name('contact.complate');
```

```powershell
PS > php artisan route:list
+--------+----------+------------------+------------------+--------------------------------------------------+--------------+
| Domain | Method   | URI              | Name             | Action                                           | Middleware   |
+--------+----------+------------------+------------------+--------------------------------------------------+--------------+
|        | GET|HEAD | /                | index            | App\Http\Controllers\IndexController             | web          |
|        | GET|HEAD | api/user         |                  | Closure                                          | api,auth:api |
|        | GET|HEAD | contact          | contact.index    | App\Http\Controllers\ContactsController@index    | web          |
|        | POST     | contact          | contact.send     | App\Http\Controllers\ContactsController@send     | web          |
|        | GET|HEAD | contact/complate | contact.complate | App\Http\Controllers\ContactsController@complate | web          |
+--------+----------+------------------+------------------+--------------------------------------------------+--------------+
```

##### 実行結果

| contact | contact/complate |
|---|---|
| ![](https://qiita-image-store.s3.amazonaws.com/0/262177/89d4d269-9479-0dea-6572-72142a08bb53.png) | ![](https://qiita-image-store.s3.amazonaws.com/0/262177/b5b21382-7ae0-197d-11fd-a9fc96fb3ebf.png) |

http://localhost:8000/contact にアクセスし、フォームを送信し完了ページが出た後に、`storage/logs/laravel.log`に入力した情報が出力されていれば完了です。

```text:storage/logs/laravel.log
[2018-07-14 02:03:53] local.NOTICE: array (
  '名前' => 'とろゝ',
  'メールアドレス' => 'toro_ponz@hoge.jp',
  '問い合わせ内容' => 'お問い合わせ内容です。お問い合わせ内容です。お問い合わせ内容です。お問い合わせ内容です。',
)
```

実際にはここでメールを送信する処理を入れたり、Slackに通知したり、DBに保存したりしますが、それはまた別の機会で。

## 次回
次回はModelについて解説します。

## 参考サイト
* [Controllers - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/5.5/controllers)
* [コントローラ 5.5 Laravel](https://readouble.com/laravel/5.5/ja/controllers.html)
* [Laravel Collective](https://laravelcollective.com/docs/master/html)