Laravelで作るRESTなWebアプリ

#はじめに
プライベートでPHPフレームワークのLaravelを使ってWebアプリ制作を行っています。
Laravelにはルーティングという機能があり、特定のアドレスが特定のHTTPメソッドでリクエストされた際に、指定したコントローラの指定したメソッドを呼び出すという仕組みがあります。

```web.php
<?php

Route::get('hello', 'HelloController@index');
Route::post('hello', 'HelloController@post');
```

```HelloController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HelloController extends Controller
{
    public function index(Request $request){
        return view('hello.index', ['msg' => "GETが送信されました"]);
    }
    
    public function post(Request $request){
        return view('hello.index',['msg' => "POSTが送信されました"]);
    }
}
```

上記の例では、**hello**というアクションがGETで呼び出された場合は**HelloController**内に記述された**indexメソッド**を呼びます。
同様に、POSTで呼び出された場合は**postメソッド**が呼び出されます。
アクションが同じでも、呼び出す際に指定したHTTPメソッドによって呼び出し先を変えることができるという点が非常に面白いです。

このように**HTTPメソッドごとにレスポンスを変化させことができるWebアプリはRESTなWebサービス**と呼ばれます。
上記の通り**Laravelのルーティング機能を使うことでRESTなWebサービスも開発できる**ことから、RESTについての興味を持ち始めました。

今回はLaravelを使ってRESTな簡単なWebサービスを作成する方法を記載したいと思います。

#RESTであるためのルール
単にHTTPメソッドごとにレスポンスを変化させるだけではRESTであるとは呼べません。
RESTの提唱者であるRoy Fielding氏はRESTの設計原則は主に以下４つからなると述べています。

>- *セッションなどの状態管理を行わない*
>- *情報を操作する命令の体系が予め定義・共有されている*
>- *すべての情報は汎用的な構文で一意に識別される*
>- *情報の内部に、別の情報や状態へのリンクを含めることができる*

個人的に要約すると。

- ステートレスであれ
- HTTPメソッドの役割を意識して機能を分割せよ
- URIをコロコロ変えるな
- HTMLやXMLで表現できるようにせよ

上記全てが興味深い考え方ですが、今回特に注目したいのが、2つ目の項目です。

##HTTPメソッドの種類
現在利用できるHTTPメソッドは以下の9種類です。

| メソッド      | 意味              |
|:-----------------|:------------------|
| GET              | リソースの取得     |
| POST             | 子リースの作成、リソースへのデータ追加、そのほかの処理|
| PUT              | リソースの更新、リソースの作成                      |
| PATCH            | リソースの部分置換                     |
| DELETE           | リソースの削除                                    |
| HEAD             | リソースののヘッダ（メタデータ）の取得              |
| OPTION           | リソースがサポートしているメソッドの取得             |
| TRACE            | 自分宛にリクエストメッセージを返す（ループバック）試験|
| CONNECT          |プロキシ動作のトンネル接続への変更                   |

上記の中でも、システムに必要な主要機能である**CRUD特性**を満たしているのは以下の４つです。

|CRUD名| 意味      | メソッド             |
|:-----------------|:-----------------|:------------------|
|Create|作成|POST/PUT          |
|Read|読み込み|GET               |
|Update|更新|PUT/PATCH               |
|Delete|削除|DELETE            |

Laravelでは上記のCRUD特性をHTTPメソッドを切り替えながらRESTなWebサービスを作成することができます。

#Laravelで作るRESTfulなWebアプリを作る方法
LaravelではRESTを準拠したコントローラを自動で生成する仕組みがあります。

コントローラの生成はLaravelではお馴染みの**artisan**コマンドを使います。

```cmd
php artisan make:controller RestappController --resource
```

上記のコマンドで特に重要なのは最後に付与している`--resource`というオプションです。
このオプションを付与することによって、コントローラー内でCRUD特性の機能を一式セットにして登録して、扱えるようにしてくれます。
非常に親切なオプションですね。

それでは、上記のコマンドで作成した**RestappController.php**の中身を確認してみましょう。

```RestappController.php
<?php

namespace App\Http\Controllers;

use App\Restdata;
use Illuminate\Http\Request;

class RestappController extends Controller
{
    public function index()
    {
        //
    }

    public function create()
    {
        //
    }

    public function store(Request $request)
    {
        //
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        //
    }

    public function update(Request $request, $id)
    {
        //
    }

    public function destroy($id)
    {
        //
    }
}

```

各メソッドの意味は以下の通りです。

| 機能      | メソッド名              |
|:-----------------|:------------------|
| 一覧表示        | index    |
| 新規作成             | create, store|
| レコード表示          | show      |
| 更新処理      | edit, update     |
| 削除処理            | destroy  |

それでは、以下のようなルーティングの処理をweb.phpに追加します。

```web.php
<?php

Route::resource('rest','RestappController');
```

上記は、このブログの上記にも記載させていただきました**Laravelのルーティング機能**を実現している処理です。
注目べきは、Routeクラスのメソッドが**get**や**post**ではなく**resource**となっている点です。

Laravelでは以下の操作をすべて**resource**で受け取り、受け付けたHTTPメソッドごとにコントローラの各メソッドを呼び出します。


| リクエスト      | 実行するメソッド              |
|:-----------------|:------------------|
| /コントローラ名  | index    |
| /コントローラ名/create   | create|
| /コントローラ名          | store(POST送信)|
| /コントローラ名/番号| show(番号 = ID) |
| /コントローラ名/番号/edit| edit(番号 = ID) |
| /コントローラ名/番号| update(番号 = ID, PUT/PATCH送信) |
| /コントローラ名/番号| destroy(番号 = ID, DELETE 送信) |

以下で、実際に動かしながら一つ一つの機能をテストしていきます。

---

なお、今回はあらかじめ、以下のような準備を行いました。

- RestAppというLaravelプロジェクトの作成
- Postgresにて、rest-appというデータベースを作成
- rest-appデータベース内に名前とメッセージが登録可能な`restdata`というテーブルを作成
- restdataテーブル内にサンプルデータを2件登録

もしLaravelプロジェクトの内容が気になる方は[GitHub](https://github.com/MasaKuuuu/rest)にアップしておきましたので、ご確認ください。


マイグレーションやシードの設定もしているため、PHPの実行環境とPostgresがインストールされていれば同じようにテストすることができます。
`※php artisan migrete でDBを更新することをお忘れなく`

---

##indexメソッドとshowメソッドの動作確認
それでは、GETを受け付ける`RestappController.php`の**indexメソッド**と**showメソッド**を以下のように修正します。

```RestappController.php
public function index()
{
    $items = Restdata::all();
    return $items->toArray();
}

public function show($id)
{
    $items = Restdata::find($id);
    return $items->toArray();
}
```

メソッド内で宣言されているResedataクラスとはLaravelで使用可能なEloquentという機能です。
JAVAで言うところの、DTOだと思ってくださって大丈夫でしょう。
Eloquentのallメソッドやfindメソッドを用いることで、データベースのデータを全件取得したり１行だけ取得したりすることができます。

さて、では、URLを叩いて結果を確認してみましょう。

ブラウザ上でコントローラ名をGETメソッドで叩きます。（今回はビルドインサーバで行います）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/748a303a-9e10-9ea2-e0ff-e3d6b436a48b.png)

テーブル内のデータ一覧をJSON形式で取得することができました。

なぜこのようなことが実現できたかというと、**Laravelではアクションメソッドからreturnされた配列のデータはJSON形式に変換して出力してくれるからです。**

次は、１行検索を試してみます。
コントローラ名/ID でGETメソッドを叩きます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/456474e5-2ab4-9917-df25-00d0dfc2f958.png)

今度は指定したIDのデータだけを取得することができました。

このようにGETメソッドだけでも叩き方を工夫するだけで、データの取り方を変えられることが確認できました。

##storeメソッドとcreateメソッドの動作確認

次は、POSTを受け付ける**storeメソッド**に処理を追加して、データを挿入できるようにしましょう。

上記の表にある通り、createメソッドはGETを受け付けます。
実は、RESTfulアプリを作成する上で、**createメソッド**は必要ありません。
なぜなら、**createメソッド**は登録フォームを表示するだけのメソッドだからです。

本来、WebAPIなどを制作する場合は、クライアントからのリクエストを受け付ける窓口だけが存在すればよいため、画面を用意する必要がありません。
今回はせっかくなので、LaravelのRESTコントローラが気前よく用意してくれた**createメソッド**を使って、入力フォームを表示する処理だけ追加します。

```RestappController.php
public function create()
{
    return view('rest.create');
}

public function store(Request $request)
{
    $restdata = new Restdata;
    $form = $request->all();
    unset($form['_token']);
    $restdata->fill($form)->save();
    return redirect('/rest');
}
```

そして、最も重要な**storeメソッド**です。
こちらは、フォームから受け取った値を一度加工してからデータベースに格納しています。

ここでも、Eloquentが登場し、fillメソッドとsaveメソッドをメソッドチェーンで連結しています。
どちらのメッドも内部処理の想像がつくかと思いますが、fillメソッドが実行するSQL文のプレースホルダに値をバインドする処理で、saveメソッドがinsertする処理です。

次に入力フォームです。

```create.blade.php
<html>
<head>
<title>REST_FORM</title>
</head>
<body>
<form action="/rest" method="POST">
{{ csrf_field() }}
NAME:<input type="text" name="name"><br>
MESSAGE:<input type="text" name="message"><br>
<input type="submit" value="send">
</form>
</body>
</html>
```

こちらは特に説明する必要もないほどシンプルな入力フォームです。
一点だけ注意しなければならないのがformタグ内に**{{ csrf_field() }}**というタグを埋め込まなければならないという点です。
**Laravelでフォームを使ったデータ送信を行う場合は、CSRF対策のためにこのタグを入れなければExceptionが発生します。**

意図しないところでWebサービスの脆弱性が生まれないようにするための安心設計です。
（最初、この仕様に気づいておらず、エラーの原因がさっぱりわかりませんでした。。。。）

それでは、データを追加してみましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/ab5b6291-331a-ddf6-7bb1-c032b2c26be5.png)

以上のようにフォームにデータを入力して、sendを押下します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/d2c1778d-ed8c-caa3-bd4f-01383fddb021.png)

データを追加することができました。

##updateメソッドの動作確認
次はPUTメソッドを受け付ける**updateメソッド**の動作確認です。

HTMLフォームはPUT,PATCH,DELETEアクションをサポートしていないため、ひと工夫が必要です。

さきほどのフォームにちょっと手を加えます。

```create.blade.php
<html>
<head>
<title>REST_FORM</title>
</head>
<body>
<form action="/rest/1" method="POST">
{{ csrf_field() }}
<input type="hidden" name="_method" value="PUT">
NAME:<input type="text" name="name"><br>
MESSAGE:<input type="text" name="message"><br>
<input type="submit" value="send">
</form>
</body>
</html>
```

追加するのは **{{ csrf_field() }}** 直下の**<input type="hidden" name="_method" value="PUT">**というパラメータです。
このように記述することで、HTMLでもPUT送信することができます。

それでは、ID:1のユーザのコメントを更新するために、**updateメソッド**を以下のような処理を追加しましょう。


```RestappController.php
public function create()
{
    return view('rest.create');
}

public function store(Request $request)
{
    $restdata = new Restdata;
    $form = $request->all();
    unset($form['_token']);
    $restdata->fill($form)->save();
    return redirect('/rest');
}
```

**updateメソッド**を呼び出すにはコントローラ名/IDをPUTメソッドで叩きます。
（今回はform要素のaction属性にID:1を直接指定しています。）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/4b0062e8-2419-2f28-a2ce-25374922f342.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/13bb1040-7ac8-5d31-ace8-2b38517151be.png)

ID:1 の messageが**updateメソッド**により「Hello World!」から「HELLO!」に修正されました。


##destoryメソッドの動作確認
最後にdeleteを受け付ける**destoryメソッド**によるデータの削除です。
**destoryメソッド**を以下のように修正します。

```RestappController.php
public function destroy($id)
{
    $items = Restdata::find($id)->delete();
    return redirect('/rest');
}
```

Eloquent での削除処理は非常にシンプルです。
findメソッドで１行検索した後にdeleteメソッドをメソッドチェーンで連結することで、選択した行を削除することができます。

画面側はIDを指定するだけで大丈夫です。

```create.blade.php
<html>
<head>
<title>REST_FORM</title>
</head>
<body>
<form action="/rest/1" method="POST">
{{ csrf_field() }}
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="send">
</form>
</body>
</html>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/d91c5301-e416-1da6-2e65-450e44aad329.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214717/52e96c43-b5dd-b96e-3de9-ba3eed881ce5.png)

ID:1のデータを削除することができました。

#おわりに
いかがでしたでしょうか。
HTTPメソッドを切り替えるだけで、アクションを切り替えることができ、ルーティングも非常にシンプルになったと思います。
CRUD特性をすべて実装しているにも関わらず、コントローラがシンプルというのも素敵です。

DBへのアクセス処理を密結合にならないように実現することで、再利用性が高まり、利用者にも優しいサービスになります。
また、開発者目線でも便利な処理は開発者間で使いまわしやすくすることは大切ですね。

#参考図書・参考サイト
Webを支える技術 ―― HTTP，URI，HTML，そしてREST
http://gihyo.jp/book/2010/978-4-7741-4204-3

PHPフレームワーク Laravel入門
http://www.shuwasystem.co.jp/products/7980html/5258.html

IT用語時点 e-Words REST
http://e-words.jp/w/REST.html

HTTPメソッド(CRUD)についてまとめた
https://qiita.com/Ryutaro/items/a9e8d18467fe3e04068e

PUT か POST か PATCH か？
https://qiita.com/suin/items/d17bdfc8dba086d36115

Laravel 5.3 コレクション
https://readouble.com/laravel/5.3/ja/collections.html#method-toarray

Laravel 5.1 HTTPルーティング
https://readouble.com/laravel/5.1/ja/routing.html#form-method-spoofing