Laravel(5.1)でCRUD


今更ですが、Laravel5.1でのCRUDについてまとめてみます。

##前提条件

* Laravelはひと通り知っている。
* Laravelから使えるDBが存在している（ここではMySQL利用）
* その他、LAMP環境、composer等はインストール済。

私は手元のMacで、MAMP環境でテストしています。

##環境整備

###何はなくてともLaravel
私はcomposerを使ってインストールしています。

```bash
composer create-project laravel/laravel test
```
執筆時点では、5.1.4が構成されれます。

###DB

DBは何でも構いません。通常はMySQLかSqliteあたりになるでしょうか。
DBの構成に合わせて.envを設定します。

>私の環境では、config/database.php(のMySQLパート)に、
'unix_socket' => '/Applications/MAMP/tmp/mysql/mysql.sock',
を追加する必要がありました（同じ別の環境ではいらないので、何が違うか確認中）。

###その他

Linuxとかで試す場合は、storagesフォルダのパーミッションを777とかにする必要があります。

##方針

* なるべく素の状態で試せるものを作る
* というわけで、DB（Table）やModelは初期状態でも使えるusersテーブル、Userモデルを使います。
* CSSとかも標準のbootstrapを利用します。
* ルーティングはRestfulとかResourcefulとかあるけど、とりあえず独自に設定していきます。

>5.xから標準ロードされなくなったHTML Helperも使いません。

##進め方

サンプルとして何から作るのがいいか？といつも悩むのですが、私は下記のような手順が好みです。
データの一覧表示から作るので、初期データをmigrateやseedを使って準備します。

* 1.テーブルやデータの生成
* 2.一覧表示 (index)
* 3.新規登録 (show)
* 4.編集 (edit/update)
* 5.削除 (destroy)
* 6.詳細表示 (create/store)
* 7.バリデーション実装する
* 8.一覧表示画面に検索機能をつける

7.8.はCRUDとは直接は関係ないですが、実務では必須なので一緒に書いておきます。

##余談（命名規則など）

Laravelは他のフレームワークに比べ、ルーティングの自由度が高く、命名規則がそれほど厳しくありません。が、おおよそ次のようなルールで作ります。

* テーブル名は複数形 小文字 (users)
* モデル名は単数形 キャメル (User)
* モデルを主に利用するコントローラー名は複数形 キャメル (UsersController)
* ビューのフォルダ名は複数系 小文字 (users) 本当はキャメルの方がいいかも。
* ファイル名 気分次第・・・。キャメルの方がいいかも。

その他、モデルを主に利用しないControllerは、必ずしも複数系にしない。
まあ、この辺は元祖のRubyやら、よく使う他のフレームワークに合わせるとよい。

##1.テーブルやデータの準備

では、具体的な作業に入ります。

###テーブルの準備

これは、初期状態で存在しているmigrationファイルを使いましょう。

```bash
php artisan migrate
```

で、usersテーブル（や、他のテーブル）が生成されます。使うのは、usersだけです。
テーブルが生成されたかDBを見て確認して下さい。

###データの準備

せっかくなのでSeederを使います。また、データ生成には標準で組み込まれているダミーデータ生成エンジンのFakerも使ってみます。

####Seederの生成

5.1からSeederもartisanで生成できます。

```bash
php artisan make:seeder UsersSeeder
```

そうすると、database/seeds/UserSeeder.phpが生成されます。

####Seeder

Seederのコードはこんな感じ。とりあえず、25名分作ります。
passwordとかは無くてもいいのですが、まあ、一応生成しておきます。

```php
<?php

use Illuminate\Database\Seeder;
use App\User;

class UsersSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //delete all data.
    	User::truncate();

    	//faker
    	$faker = Faker\Factory::create('ja_JP');

    	//insert
    	for($i=0;$i<25;$i++)
    	{
    		$user = User::create();

    		$user->name = $faker->userName();
    		$user->email = $faker->unique()->email();
    		$user->password = Hash::make($user->email); //パスワードはとりあえずemailをハッシュ化。

    		$user->save();

    	}
    }
}
```
userの登録はワンライナーとかで書く方法もありますが、ここではわかりやすさ重視で。

###Seeder実行

Seeder自体は、php artisan db:seedで実行されますが、実行対象とするためにはdatabase/seeds/DatabaseSeeder.phpに生成したSeederを登録する必要があります。

```php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();

		 //実行対象のSeederクラス
        $this->call(UsersSeeder::class);

        Model::reguard();
    }
}
```

では、実行します。

```bash
php artisan db:seed
```

生成されたかどうか確認して下さい。

##2.一覧表示を作る

###ルート

ルートは、/usersで一覧が表示されるようにします。また、実際の処理は、UsersControllerのindex()メソッドで対応します。

```php
Route::get('users','UsersController@index');
```

###コントローラ
まずは、コントローラーを生成しましょう。

```bash
php artisan make:controller UsersController
```
とします。そうすると、indexやらcreateやらが記述されたテンプレートが生成されます。
Userモデルを使いたいので、頭のほうで

```php
use App\User;
```

として下さい。
以下は、index()のみの抜粋。

```php
    public function index()
    {
        $query = User::query();
        //全件取得
        //$users = $query->get();
        //ページネーション
        $users = $query->orderBy('id','desc')->paginate(10);
        return view('users.index')->with('users',$users);
    }
```
一覧だけなら、$users = User::all();とかでもいいのですが、ここでは$queryオブジェクトを生成して対応しています（その理由は検索機能の実装等で便利だからですが、ここでは触れません）。
また、せっかくなので、orderByで最新登録が先頭に来るようにしているのと、10行毎にページ処理をしています。
また、取得したデータはcompact()等で返してもいいのですが、わかりやすく-with()を利用しています。

>viwe('viwe名')-with('viewでの変数名','実データ'); という形式になります。


###ビュー

####共通テンプレートを作る

まず、共通のテンプレートファイルをresources/views/layout.blade.phpとして作成します。

```html
<!doctype html>
<html>
<head>
	<meta charset="utf-8">
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>

	<div class="container">
		@yield('content')
	</div>

<!-- js -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script>
	@yield('script')
</script>
</body>
</html>
```

boostrapをとりあえず利用しましょう。カスタマイズできる箇所として

* @yield('content')
* @yield('script')

の2箇所を定義しました。@yield('script')はとりあえず無くてもいいですが、最後のほうで使います。

####一覧用の画面を作る

resources/views/以下にusersフォルダを作成し、その中にindex.blade.phpを作成します。

```html
@extends('layout')

@section('content')

	<h1>一覧表示</h1>

	<table class="table table-striped">
	@foreach($users as $user)
		<tr>
			<td>{{$user->id}}</td>
			<td>{{$user->name}}</td>
			<td>{{$user->email}}</td>
		</tr>
	@endforeach
	</table>

	<!-- page control -->
	{!! $users->render() !!}

@stop
```

@extends('layout')で先ほど作成した共通レイアウトを読み込み、@section('content')...@stopでcontentを定義しています。一度、表示してみてください。

####新規登録、詳細、編集、削除ボタンをつける

データの一覧という意味では上記までのコードでいいのですが、この後必要となる新規登録、詳細、編集、削除ボタンを用意しておきましょう。index.blade.phpを更に編集し、下記のようにします。ポイントとしては、

* idはvalue等ではなくurlに含んで送る（POSTの場合も）。
* 削除(destroy)は、リンク（GET）ではなくPOSTで処理する。idはurlで送る。
* 削除ボタンにbtn-destroyというクラスを追加。

と言ったところでしょうか。

>また、restfulとかならhref="/users/{{$user->id}}/edit"とする方がいいのですけど、まあ、わかりやすさ重視で。

```html
@extends('layout')

@section('content')

	<h1>一覧表示</h1>

	<div class="row">
		<div class="col-sm-12">
			<a href="/users/create" class="btn btn-primary" style="margin:20px;">新規登録</a>
		</div>
	</div>

	<!-- table -->
	<table class="table table-striped">

	<!-- loop -->
	@foreach($users as $user)
		<tr>
			<td>{{$user->id}}</td>
			<td>{{$user->name}}</td>
			<td>{{$user->email}}</td>
			<td><a href="/users/show/{{$user->id}}" class="btn btn-primary btn-sm">詳細</a></td>
			<td><a href="/users/edit/{{$user->id}}" class="btn btn-primary btn-sm">編集</a></td>
			<td>
				<form method="post" action="/users/destroy/{{$user->id}}">
					<input type="hidden" name="_token" value="{{csrf_token()}}">
					<input type="submit" value="削除" class="btn btn-danger btn-sm btn-destroy">
				</form>
			</td>
		</tr>
	@endforeach
	</table>

	<!-- page control -->
	{!! $users->render() !!}

@stop
```

一度見てみましょう。

![crud](http://www.bluecode.jp/qiita/lara_crud001.png)

こんな感じになっています。
各ボタンにマウスオーバーし、id等がちゃんとリンク先に渡されるようにURLが生成されているか確認して下さい。

>上記メールアドレスはfakerで生成したダミーです。が、偶然一致もあるでしょからモザイク処理しています。念のため。

これで、一覧表示機能は完成です。

##3.新規登録

では、新規登録を作成します。新規登録では2つのルートと機能を設定します。

* create : 入力画面の生成とstoreへのデータの送信。
* store : 情報を受け取り保存（一覧へリダイレクト）。

###ルート
ルートは、createとstoreを設定します。storeの方はpostになります。

```php
Route::get('users/create','UsersController@create');

Route::post('users/store','UsersController@store');

```

###コントローラー

まず、create。

```php
    public function create()
    {
        //createに転送
        return view('users.create');
    }
```
基本的に、users.create viewに処理を転送しているだけ。
そして、store。
createが投げてきた値を受け取り、DBに保存。そして、一覧表示へリダイレクトしているだけ。

```php
    public function store(Request $request)
    {
        //userオブジェクト生成
        $user = User::create();

        //値の登録
        $user->name = $request->name;
        $user->email = $request->email;

        //保存
        $user->save();

        //一覧にリダイレクト
        return redirect()->to('/users');
    }
```

###ビュー
users以下に、create.blade.phpを作成し、下記のように記述。

```html
@extends('layout')

@section('content')

	<h1>新規作成</h1>

	<div class="row">
		<div class="col-sm-12">
			<a href="/users" class="btn btn-primary" style="margin:20px;">一覧に戻る</a>
		</div>
	</div>

	<!-- form -->
	<form method="post" action="/users/store">

		<div class="form-group">
			<label>名前</label>
			<input type="text" name="name" value="" class="form-control">
		</div>

		<div class="form-group">
			<label>E-Mail</label>
			<input type="text" name="email" value="" class="form-control">
		</div>

		<input type="hidden" name="_token" value="{{csrf_token()}}">

		<input type="submit" value="登録" class="btn btn-primary">

	</form>

@stop
```
特に難しいことは何もしていない。

* post先(action)はstore(/users/store)。methodはpost。
* hiddenでLaravelでpostするときに原則必要となるcsrf_tokenを送っている。

>エラー処理は後でやる。

見た目はこんな感じ。なお、とりあえず登録情報は名前とemailだけにしてあります。

![crud](http://www.bluecode.jp/qiita/lara_crud002.png)

動作確認してみてください。一覧の一番上に最新の登録内容が記載されているはずです。

##4.編集

次に、各レコードの編集機能を作ります。基本は、新規作成の応用です。
新規の時と同様、2つのルートと機能（メソッド）を作ります。

* edit : 編集画面を表示し、updateにデータを送る。
* update : editから送られたデータを受け取り更新する。

###ルート

ルートとしては、editとupdateを追加。

```php
Route::get('users/edit/{id}','UsersController@edit');

Route::post('users/update/{id}','UsersController@update');
```

それぞれ、編集対象となるidを{id}として受け取ります。また、updateはpostとなります。

###コントローラー

まず、edit。

```php
    public function edit($id)
    {
        //レコードを検索
        $user = User::find($id);
        //検索結果をビューに渡す
        return view('users.edit')->with('user',$user);
    }
```
受け取ったidを元に、レコードを検索し、その情報をviewに返します。
そして、update。受け取ったidを元にレコードを検索、更新し、一覧へリダイレクトさせています。

```php
    public function update(Request $request, $id)
    {
        //レコードを検索
        $user = User::find($id);
        //値を代入
        $user->name = $request->name;
        $user->email = $request->email;
        //保存（更新）
        $user->save();
        //リダイレクト
        return redirect()->to('/users');
    }
```

###ビュー
基本的に構成はcreateと同じなので、コピーしてedit.blade.phpとし、必要な箇所を編集します。

```html
@extends('layout')

@section('content')

	<h1>情報編集</h1>

	<div class="row">
		<div class="col-sm-12">
			<a href="/users" class="btn btn-primary" style="margin:20px;">一覧に戻る</a>
		</div>
	</div>

	<!-- form -->
	<form method="post" action="/users/update/{{$user->id}}">

		<div class="form-group">
			<label>名前</label>
			<input type="text" name="name" value="{{$user->name}}" class="form-control">
		</div>

		<div class="form-group">
			<label>E-Mail</label>
			<input type="text" name="email" value="{{$user->email}}" class="form-control">
		</div>

		<input type="hidden" name="_token" value="{{csrf_token()}}">

		<input type="submit" value="更新" class="btn btn-primary">

	</form>

@stop
```

createとの主な違いは、

* action先が/user/update/{id}になる（idはpostせずurlに含める）。
* name,emailともに初期値として、既存値をvalue={{$user->name}}などとして表示。
* submitとのvalueを「更新」に。

見た目は省略。

##5.詳細表示

ここで、各レコードの詳細表示画面を作ってみます。

###ルート

ルートはshowを定義します。

```php
Route::get('users/show/{id}','UsersController@show');
```

詳細表示対象となるidを{id}として受け取ります。

###コントローラー

show()メソッドを定義します。
idでレコードを検索し、その結果をそのままviweに返します。

```php
    public function show($id)
    {
        //レコードを検索
        $user = User::find($id);
        //検索結果をビューに渡す
        return view('users.show')->with('user',$user);
    }
```

###ビュー

show.blade.phpを作成し、編集します。
お好みで表示すればいいのですが、ここはテーブルにしました。

```html
@extends('layout')

@section('content')

	<h1>詳細表示</h1>

	<div class="row">
		<div class="col-sm-12">
			<a href="/users" class="btn btn-primary" style="margin:20px;">一覧に戻る</a>
		</div>
	</div>

	<!-- table -->
	<table class="table table-striped">
		<tr><td>ID</td><td>{{$user->id}}</tr>
		<tr><td>名前</td><td>{{$user->name}}</tr>
		<tr><td>E-Mail</td><td>{{$user->email}}</tr>
	</table>
	
@stop
```
難しいところは何もありません。見た目は、こんな感じ。

![crud](http://www.bluecode.jp/qiita/lara_crud003.png)

##6.削除

削除も全く難しくありません。強いて言うなら、一覧のところでformで削除処理を書くところくらいでしょうか。

###ルート

destroyを定義します。受け付けるメソッドはpostとします。

```php
Route::post('users/destroy/{id}','UsersController@destroy');
```

###コントローラー
対象となるレコードを取得し、削除。その後、一覧へリダイレクトしています。

```php
    public function destroy($id)
    {
        //削除対象レコードを検索
        $user = User::find($id);
        //削除
        $user->delete();
        //リダイレクト
        return redirect()->to('/users');
    }
```

###ビュー

ビューはありません。が、この仕様だと、何の警告もなく削除されるのでcomfirmくらい入れておきます。
index.blade.phpを編集します（下記抜粋）。

削除ボタンには既にbtn-destroy classが設定されているので、@section('script')を設け、下記コードを追加します。

```html
@section('script')
$(function(){
	$(".btn-destroy").click(function(){
		if(confirm("本当に削除しますか？")){
			//そのままsubmit（削除）
		}else{
			//cancel
			return false;
		}
	});
});
@stop
```

これで確認ダイアログが表示されるようになりました。

>ここでは、layout側に\<script>タグを記述しましたが、シンタックスハイライト等を考えると、個別コード側に\<script>を記述した方がいいかもしれません。


##7.バリデーションをつける

さらに実用性を上げるためには保存時（新規、更新）にバリデーション処理を追加します。Laravel5.xでは、従来のバリデーションと、RequestFormを使ったバリデーションが使えます。

ここでは新規保存(create/store）を例に、従来型のバリデーションを実装してみます。また、評価だけでなく、エラーメッセージの表示についても実装してみます。

###従来型のValidation


####コントローラー

バリデーションは保存前に行うのでstore()で行うことになります。
store()メソッドは次のようになります。

```php
    public function store(Request $request)
    {
        //バリデーション

        //評価対象
        $inputs = $request->all();

        //ルール
        $rules = [
            'name'=>'required',
            'email'=>'required|email|unique:users',
        ];

        $messages = [
            'name.required'=>'名前は必須です。',
            'email.required'=>'emailは必須です。',
            'email.email'=>'emailの形式で入力して下さい。',
            'email.unique'=>'このemailは既に登録されています。',
        ];

        $validation = \Validator::make($inputs,$rules,$messages);

        //エラー次の処理
        if($validation->fails())
        {
            return redirect()->back()->withErrors($validation->errors())->withInput();
        }

        //バリデーションOKなら、今まで通り。


        //userオブジェクト生成
        $user = User::create();

        //値の登録
        $user->name = $request->name;
        $user->email = $request->email;

        //保存
        $user->save();

        //一覧にリダイレクト
        return redirect()->to('/users');
    }
```

バリデーションの実体はValidator::make()になります。ここに、評価対象、評価ルール、エラーメッセージ（オプション）を渡し、評価結果を得ます。$validation-.fails()でNGだった場合は、呼び出し元のviewにリダイレクトし、OKなら、登録処理に移ります。

なお、エラー時に、リダイレクト文を、

```php
return redirect()->back()->withErrors($validation->errors())->withInput();
```

とすることで、エラー内容および、元々の入力値を呼び出し元のビューに戻すことができ、ビュー側でのエラー表示等に利用することができます。

####ビュー

エラーメッセージ等の表示は、リクエスト元であるcreate(view)に記述します。
create.blade.phpを下記のようにします。

```html
@extends('layout')

@section('content')

	<h1>新規作成</h1>

	<div class="row">
		<div class="col-sm-12">
			<a href="/users" class="btn btn-primary" style="margin:20px;">一覧に戻る</a>
		</div>
	</div>

	<!-- form -->
	<form method="post" action="/users/store">

		<!-- エラーがあるかどうかを判断して、has-errorクラスを追加 -->
		<div class="form-group @if(!empty($errors->first('name'))) has-error @endif">
			<label>名前</label>
			<input type="text" name="name" value="{{Input::old('name')}}" class="form-control">
			<!-- (最初の）エラーメッセージ表示 -->
			<span class="help-block">{{$errors->first('name')}}</span>
		</div>

		<!-- エラーがあるかどうかを判断して、has-errorクラスを追加 -->
		<div class="form-group @if(!empty($errors->first('email'))) has-error @endif">
			<label>E-Mail</label>
			<input type="text" name="email" value="{{Input::old('email')}}" class="form-control">
			<!-- (最初の）エラーメッセージ表示 -->
			<span class="help-block">{{$errors->first('email')}}</span>
		</div>

		<input type="hidden" name="_token" value="{{csrf_token()}}">

		<input type="submit" value="登録" class="btn btn-primary">

	</form>

@stop
```

色々追加されていますが、nameを例に説明すると、ポイントは、

* <span class="help-block">{{$errors->first('name')}}</span>でのエラーメッセージ表示。
* value="{{Input::old('name')}}"での入力値保存。
* \<div class="form-group @if(!empty($errors->first('name'))) has-error @endif">でのhas-errorクラスの追加（赤くする処理）

の３つです。エラーが発生すると、下記のように表示されます。
![crud](http://www.bluecode.jp/qiita/lara_crud004.png)

ReqestFormを利用すると、コントローラーが簡潔になりますが、ここでは、ここまでとします。

RequestFormを使用した方法にちては[こちら](http://qiita.com/zaburo/items/f0db54bd3ebd81a8ce68)をどうぞ。

なお、updateにてuniqueバリデーションを使う際は、少し記述を変える必要があります。
詳しくは、[こちら](https://teamtreehouse.com/community/laravel-unique-email-update)参考に。

##8.一覧表示画面に検索機能をつける

一覧表示には通常、検索機能が必要になるので、簡単なサンプルを記載しておきます。
こんな感じ。

![crud](http://www.bluecode.jp/qiita/lara_crud005.png)

追加すべき機能は、

* index.bladeに検索フォームを追加。
* index()に検索機能を追加（検索は複数カラムのor,like検索）。
* 検索結果のページングの際にkeywordを持ちまわるようにする。

の大きく３つ。

###ビュー

まずは、index.blade.phpに検索フォームを追加します。また、ページングしてもkeywordを引き継げるよにrender()の記述を変更します（以下、@section('content')のみ抜粋）。

```html
@section('content')

	<h1>一覧表示</h1>

	<!-- 新規登録ボタン -->
	<div class="row">
		<div class="col-sm-12">
			<a href="/users/create" class="btn btn-primary" style="margin:20px;">新規登録</a>
		</div>
	</div>

	<!-- 検索フォーム -->
	<div class="row">
		<div class="col-sm-12">
			<form method="get" action="/users" class="form-inline" style="margin:20px;">
				<div class="form-group">
					<label>検索</label>
					<input type="text" name="keyword" class="form-control" value="{{$keyword}}">
				</div>
				<input type="submit" value="検索" class="btn btn-info">
			</form>
		</div>
	</div>

	<!-- table -->
	<table class="table table-striped">

	<!-- loop -->
	@foreach($users as $user)
		<tr>
			<td>{{$user->id}}</td>
			<td>{{$user->name}}</td>
			<td>{{$user->email}}</td>
			<td><a href="/users/show/{{$user->id}}" class="btn btn-primary btn-sm">詳細</a></td>
			<td><a href="/users/edit/{{$user->id}}" class="btn btn-primary btn-sm">編集</a></td>
			<td>
				<form method="post" action="/users/destroy/{{$user->id}}">
					<input type="hidden" name="_token" value="{{csrf_token()}}">
					<input type="submit" value="削除" class="btn btn-danger btn-sm btn-destroy">
				</form>
			</td>
		</tr>
	@endforeach
	</table>

	<!-- page control -->
	{!! $users->appends(['keyword'=>$keyword])->render() !!}

@stop
```
{!! $users->appends(['keyword'=>$keyword])->render() !!}が持ち回りする$keywordは、コントローラーから返してもらいます。

###コントローラー

ここでは、検索用のルートやメソッドを新規に定義はせず、index()メソッドを改良します。

```php
    public function index()
    {
        //キーワード受け取り
        $keyword = \Input::get('keyword');

        //クエリ生成
        $query = User::query();

        //もしキーワードがあったら
        if(!empty($keyword))
        {
            $query->where('email','like','%'.$keyword.'%')->orWhere('name','like','%'.$keyword.'%');
        }

        //ページネーション
        $users = $query->orderBy('id','desc')->paginate(10);
        return view('users.index')->with('users',$users)
                                  ->with('keyword',$keyword);
    }
```

keywordが送られてきているかどうかを判断し、送られていなければ、通常の処理（全検索）を行い、keywordがあれば、where句を追加した検索を行っています。また、keywordの持ち回りのため、送られてきたkeywordを-with('keyword',$keyword)でビューに戻しています。

```php
$query->where('email','like','%'.$keyword.'%')->orWhere('name','like','%'.$keyword.'%');
```

は、1つキーワードでemailとnameカラムの両方をorでlike検索しています。1つのカラムだけに絞りたければ、orWhereを消せばいい。また、AND検索をしたい場合は、

```php
$query->where('name','like','%'.$keyword.'%');
$query->where('email','like','%'.$keyword.'%');
```

など記述すれば、条件が追記される（チェーンでつなげてもいい）。