今更ですが、Laravel5.1でのCRUDについてまとめてみます。
##前提条件
- Laravelはひと通り知っている。
- Laravelから使えるDBが存在している(ここではMySQL利用)
- その他、LAMP環境、composer等はインストール済。
私は手元のMacで、MAMP環境でテストしています。
##環境整備
###何はなくてともLaravel
私はcomposerを使ってインストールしています。
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ファイルを使いましょう。
php artisan migrate
で、usersテーブル(や、他のテーブル)が生成されます。使うのは、usersだけです。
テーブルが生成されたかDBを見て確認して下さい。
###データの準備
せっかくなのでSeederを使います。また、データ生成には標準で組み込まれているダミーデータ生成エンジンのFakerも使ってみます。
####Seederの生成
5.1からSeederもartisanで生成できます。
php artisan make:seeder UsersSeeder
そうすると、database/seeds/UserSeeder.phpが生成されます。
####Seeder
Seederのコードはこんな感じ。とりあえず、25名分作ります。
passwordとかは無くてもいいのですが、まあ、一応生成しておきます。
<?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
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();
}
}
では、実行します。
php artisan db:seed
生成されたかどうか確認して下さい。
##2.一覧表示を作る
###ルート
ルートは、/usersで一覧が表示されるようにします。また、実際の処理は、UsersControllerのindex()メソッドで対応します。
Route::get('users','UsersController@index');
###コントローラ
まずは、コントローラーを生成しましょう。
php artisan make:controller UsersController
とします。そうすると、indexやらcreateやらが記述されたテンプレートが生成されます。
Userモデルを使いたいので、頭のほうで
use App\User;
として下さい。
以下は、index()のみの抜粋。
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として作成します。
<!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をとりあえず利用しましょう。カスタマイズできる箇所として
の2箇所を定義しました。@yield('script')はとりあえず無くてもいいですが、最後のほうで使います。
####一覧用の画面を作る
resources/views/以下にusersフォルダを作成し、その中にindex.blade.phpを作成します。
@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"とする方がいいのですけど、まあ、わかりやすさ重視で。
@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
一度見てみましょう。
こんな感じになっています。
各ボタンにマウスオーバーし、id等がちゃんとリンク先に渡されるようにURLが生成されているか確認して下さい。
上記メールアドレスはfakerで生成したダミーです。が、偶然一致もあるでしょからモザイク処理しています。念のため。
これで、一覧表示機能は完成です。
##3.新規登録
では、新規登録を作成します。新規登録では2つのルートと機能を設定します。
- create : 入力画面の生成とstoreへのデータの送信。
- store : 情報を受け取り保存(一覧へリダイレクト)。
###ルート
ルートは、createとstoreを設定します。storeの方はpostになります。
Route::get('users/create','UsersController@create');
Route::post('users/store','UsersController@store');
###コントローラー
まず、create。
public function create()
{
//createに転送
return view('users.create');
}
基本的に、users.create viewに処理を転送しているだけ。
そして、store。
createが投げてきた値を受け取り、DBに保存。そして、一覧表示へリダイレクトしているだけ。
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を作成し、下記のように記述。
@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だけにしてあります。
動作確認してみてください。一覧の一番上に最新の登録内容が記載されているはずです。
##4.編集
次に、各レコードの編集機能を作ります。基本は、新規作成の応用です。
新規の時と同様、2つのルートと機能(メソッド)を作ります。
- edit : 編集画面を表示し、updateにデータを送る。
- update : editから送られたデータを受け取り更新する。
###ルート
ルートとしては、editとupdateを追加。
Route::get('users/edit/{id}','UsersController@edit');
Route::post('users/update/{id}','UsersController@update');
それぞれ、編集対象となるidを{id}として受け取ります。また、updateはpostとなります。
###コントローラー
まず、edit。
public function edit($id)
{
//レコードを検索
$user = User::find($id);
//検索結果をビューに渡す
return view('users.edit')->with('user',$user);
}
受け取ったidを元に、レコードを検索し、その情報をviewに返します。
そして、update。受け取ったidを元にレコードを検索、更新し、一覧へリダイレクトさせています。
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とし、必要な箇所を編集します。
@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を定義します。
Route::get('users/show/{id}','UsersController@show');
詳細表示対象となるidを{id}として受け取ります。
###コントローラー
show()メソッドを定義します。
idでレコードを検索し、その結果をそのままviweに返します。
public function show($id)
{
//レコードを検索
$user = User::find($id);
//検索結果をビューに渡す
return view('users.show')->with('user',$user);
}
###ビュー
show.blade.phpを作成し、編集します。
お好みで表示すればいいのですが、ここはテーブルにしました。
@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
難しいところは何もありません。見た目は、こんな感じ。
##6.削除
削除も全く難しくありません。強いて言うなら、一覧のところでformで削除処理を書くところくらいでしょうか。
###ルート
destroyを定義します。受け付けるメソッドはpostとします。
Route::post('users/destroy/{id}','UsersController@destroy');
###コントローラー
対象となるレコードを取得し、削除。その後、一覧へリダイレクトしています。
public function destroy($id)
{
//削除対象レコードを検索
$user = User::find($id);
//削除
$user->delete();
//リダイレクト
return redirect()->to('/users');
}
###ビュー
ビューはありません。が、この仕様だと、何の警告もなく削除されるのでcomfirmくらい入れておきます。
index.blade.phpを編集します(下記抜粋)。
削除ボタンには既にbtn-destroy classが設定されているので、@section('script')を設け、下記コードを追加します。
@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()メソッドは次のようになります。
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なら、登録処理に移ります。
なお、エラー時に、リダイレクト文を、
return redirect()->back()->withErrors($validation->errors())->withInput();
とすることで、エラー内容および、元々の入力値を呼び出し元のビューに戻すことができ、ビュー側でのエラー表示等に利用することができます。
####ビュー
エラーメッセージ等の表示は、リクエスト元であるcreate(view)に記述します。
create.blade.phpを下記のようにします。
@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を例に説明すると、ポイントは、
- {{$errors->first('name')}}でのエラーメッセージ表示。
- value="{{Input::old('name')}}"での入力値保存。
- <div class="form-group @if(!empty($errors->first('name'))) has-error @endif">でのhas-errorクラスの追加(赤くする処理)
ReqestFormを利用すると、コントローラーが簡潔になりますが、ここでは、ここまでとします。
RequestFormを使用した方法にちてはこちらをどうぞ。
なお、updateにてuniqueバリデーションを使う際は、少し記述を変える必要があります。
詳しくは、こちら参考に。
##8.一覧表示画面に検索機能をつける
一覧表示には通常、検索機能が必要になるので、簡単なサンプルを記載しておきます。
こんな感じ。
追加すべき機能は、
- index.bladeに検索フォームを追加。
- index()に検索機能を追加(検索は複数カラムのor,like検索)。
- 検索結果のページングの際にkeywordを持ちまわるようにする。
の大きく3つ。
###ビュー
まずは、index.blade.phpに検索フォームを追加します。また、ページングしてもkeywordを引き継げるよにrender()の記述を変更します(以下、@section('content')のみ抜粋)。
@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()メソッドを改良します。
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)でビューに戻しています。
$query->where('email','like','%'.$keyword.'%')->orWhere('name','like','%'.$keyword.'%');
は、1つキーワードでemailとnameカラムの両方をorでlike検索しています。1つのカラムだけに絞りたければ、orWhereを消せばいい。また、AND検索をしたい場合は、
$query->where('name','like','%'.$keyword.'%');
$query->where('email','like','%'.$keyword.'%');
など記述すれば、条件が追記される(チェーンでつなげてもいい)。