Help us understand the problem. What is going on with this article?

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

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

zaburo
こんにちは。自分用のメモをだらだら公開しています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away