PHP
laravel5

Laravel(5.1)でCRUD

More than 3 years have passed since last update.

今更ですが、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

一度見てみましょう。

crud

こんな感じになっています。

各ボタンにマウスオーバーし、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だけにしてあります。

crud

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


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

難しいところは何もありません。見た目は、こんな感じ。

crud


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クラスの追加(赤くする処理)

の3つです。エラーが発生すると、下記のように表示されます。

crud

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

RequestFormを使用した方法にちてはこちらをどうぞ。

なお、updateにてuniqueバリデーションを使う際は、少し記述を変える必要があります。

詳しくは、こちら参考に。


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

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

こんな感じ。

crud

追加すべき機能は、


  • 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.'%');

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