はじめに

Laravelの使い方を勉強するため、単一のテーブルに対するCRUDを実装したときのメモ。

インストール

Composerが必要なのでインストールしておく。Laravelはターミナルからcomposerコマンドでインストールする。

$ composer create-project --prefer-dist laravel/laravel sample

sampleというフォルダが作成される。sampleフォルダにて、

$ php artisan serve

とするとビルトインサーバーが起動してhttp://localhost:8000 でアクセスできる。

設定

config/app.php にtimezoneとlocaleを設定。

// config/app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',

.env にDB接続情報を設定。

ルーティング

routes/web.php にて設定を行う。様々な記述が可能だけど今回はCRUDを実装したかったので、

Route::resource('sample', 'SampleController');

とした。こうすることで以下のルーティングに対応してくれる。

動詞 URI アクション ルート名
GET /sample index sample.index
GET /sample/create create sample.create
POST /sample store sample.store
GET /sample/{sample} show sample.show
GET /sample/{sample}/edit edit sample.edit
PUT/PATCH /sample/{sample} update sample.update
DELETE /sample/{sample} destroy sample.destroy

ちなみに、

$ php artisan route:list

で設定されているルーティングを一覧表示することができる。

マイグレーション

今回は別のサンプルで使用したテーブルを使いまわしたのでマイグレーションは使用しない。

テンプレート

ヘッダー/フッターなどの共通部分レイアウトのファイルをresources/views/layouts/master.blade.php に作成した。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>@yield('title')</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link type="text/css" rel="stylesheet" href="/css/bootstrap.min.css">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
</head>
<body>
    <!-- 共通部分 -->
    <div class="container">
        @yield('content')
    </div>
    <!-- 共通部分 -->
    <script src="/js/bootstrap.min.js">
</body>
</html>

レイアウトに基づいた各ページは

@extends('layouts.master')

@section('title', 'Sample')

@section('content')
    <!-- ページ内容 -->
@endsection

となる。

コントローラー

artisanコマンドを使えばファイルを生成してくれる。

$ php artisan make:controller SampleController --resource

--resouces オプションで前述したルーティング設定に基づいたメソッドが作成される。

モデル

こちらもartisanコマンドで生成される。

$ php artisan make:model Sample
// app/Sample.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Sample extends Model
{
    protected $guarded = [];
}

protected $guarded = []; を追加した。後述する新規作成で使用するcreate メソッドで必須となるパラメータで、指定したプロパティはcreateの対象としない(空の配列だと指定なしとなり全プロパティが対象となる)。

一覧表示

検索フォーム

// resources/views/sample/index.blade.php
<form method="get" action="{{ url('sample') }}" class="form-inline" role="form">
    <div class="form-group">
        <label class="sr-only" for="name">名前</label>
        <input type="text" name="name" value="{{ request('name') }}" id="name" class="form-control" placeholder="名前">
    </div>
    <div class="form-group">
        <label class="sr-only" for="email">メールアドレス</label>
        <input type="text" name="email" value="{{ request('email') }}" id="email" class="form-control" placeholder="メールアドレス">
    </div>
    <input type="submit" name="btn" value="検索" class="btn">
</form>

波括弧で括られた箇所が動的に生成される。それぞれヘルパー関数を使用している。埋め込まれている箇所と関数名から大体想像がつくと思うので説明は省略する。

一覧表示

// resources/views/sample/index.blade.php
@if (count($results) > 0)
    <table class="table table-striped table-bordered table-condensed">
        <thead>
            <tr>
                <th>ID</th>
                <th>名前</th>
                <th>メールアドレス</th>
                <th>編集</th>
                <th>削除</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($results as $result)
                <tr>
                    <td>{{ $result->id }}</td>
                    <td><a href="{{ route('sample.show', [$result->id]) }}">{{ $result->name }}</a></td>
                    <td>{{ $result->email }}</td>
                    <td><a href="{{ route('sample.edit', [$result->id]) }}" class="btn btn-default">編集</a></td>
                    <td>
                        <form method="post" action="{{ route('sample.destroy', [$result->id]) }}" style="display:inline;">
                            {!! csrf_field() !!}
                            {{ method_field('DELETE') }}
                            <button type="submit" class="btn btn-danger" onclick="return confirm('削除します。よろしいですか?')">削除</button>
                        </form>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
    {{ $results->links() }}
@else
    <p>登録されているデータはありません。</p>
@endif

Bladeテンプレートの制御構文@if や@foreach で条件による出し分けやループの処理を行う。

削除処理では{{ method_field('DELETE') }} でDELETEメソッドとして扱うことができるようになる。また、{!! csrf_field() !!} でCSRFトークンが埋め込まれる。

{{ $results->links() }} にはページネーションで生成されたページング表示が埋め込まれる。

結果表示

// resources/views/sample/index.blade.php
@if (session('success'))
    <div class="alert alert-success">
        <button class="close" data-dismiss="alert">&times;</button>
        {{ session('success') }}
    </div>
@endif
@if (session('error'))
    <div class="alert alert-error">
        <button class="close" data-dismiss="alert">&times;</button>
        {{ session('error') }}
    </div>
@endif

新規作成/編集/削除時、実行結果をセッションのフラッシュデータに保持して一覧画面へリダイレクトしてくるので、その表示用。

コントローラー

// app/Http/Controllers/SampleController.php
public function index(Request $request)
{
    $sample = Sample::query();

    if ($request->input('name')) {
        $sample->where('name', 'like', '%'.$request->input('name').'%');
    }

    if ($request->input('email')) {
        $sample->where('email', 'like', '%'.$request->input('email').'%');
    }

    $results = $sample->orderBy('id', 'desc')->paginate(20);

    return view('sample.index', ['results' => $results]);
}

こちらも大体見て想像がつくと思うので説明は省略する。使用するクラスはuse で宣言する必要がある。

新規作成

入力フォーム

// resources/views/sample/create.blade.php
<form method="post" action="{{ url('sample') }}">
    {!! csrf_field() !!}
    <div class="form-group">
        <label for="name" class="col-sm-2 control-label">名前*</label>
        <div class="col-sm-10">
            <input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}">
        </div>
    </div>
    <div class="form-group">
        <label for="email" class="col-sm-2 control-label">メールアドレス*</label>
        <div class="col-sm-10">
            <input type="text" name="email" id="email" class="form-control" value="{{ old('email') }}">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <input type="submit" value="作成" class="btn">
        </div>
    </div>
</form>

ヘルパー関数のold でセッションのフラッシュデータとして保持されている入力値を埋め込んでいる。

入力エラー表示

// resources/views/sample/create.blade.php
@if (count($errors) > 0)
    <div class="alert alert-danger">
        <a class="close" data-dismiss="alert">&times;</a>
        @foreach ($errors->all() as $error)
            {{ $error }}<br>
        @endforeach
    </div>
@endif

あまり実践的ではない書き方(入力項目の下に個別に表示するのが望ましい)ですいません。

バリデーション

バリデーションはフォームリクエストとして別ファイルに書くことができる。artisanコマンドを使えばファイルを生成することができる。

$ php artisan make:request SampleRequest

authorize rules というメソッドが用意されていて

// app/Http/Request/SampleRequest.php
public function authorize()
{
    return true;
}

authorizeにはリクエストに対する認証が設定できる。今回は何もしないのでtrue を返すだけ。

// app/Http/Request/SampleRequest.php
public function rules()
{
    return [
        'name' => 'required|max:255',
        'email' => [
            'required',
            'max:255',
            'email',
            Rule::unique('samples')->ignore($this->sample),
        ],
    ];
}

rulesにバリデーションルールを設定する。Rule::unique('samples')->ignore($this->sample), がemailが一意かどうかをチェックして、編集時には自分のIDを除外する、といった指定になる。この指定の仕方がググっても情報がマチマチだったので別記事にまとめておきたい。

この設定を入れておけば入力エラー時にエラー内容を持って元の画面へリダイレクトしてくれる。

日本語版バリデーションメッセージはこちらを使わせていただきました。

https://gist.github.com/syokunin/b37725686b5baf09255b

resources/lang/ja/validation.php に設置。

コントローラー

// app/Http/Controllers/SampleController.php
public function create()
{
    return view('sample.create');
}

public function store(SampleRequest $request)
{
    if ($sample = Sample::create($request->only(['name', 'email']))) {
        $request->session()->flash('success', '作成しました。');
    } else {
        $request->session()->flash('error', '作成に失敗しました。');
    }

    return redirect('sample');
}

SampleRequest $request でフォームリクエストを指定している。

詳細表示

詳細表示

// resources/views/sample/show.blade.php
<table class="table table-striped table-bordered table-condensed">
    <tbody>
        <tr>
            <th>名前</th>
            <td>{{ $result->name }}</td>
        </tr>
        <tr>
            <th>メールアドレス</th>
            <td>{{ $result->email }}</td>
        </tr>
    </tbody>
</table>

サンプルにつき、詳細といいながら一覧と同じ項目しか出力できていない。

コントローラー

// app/Http/Controllers/SampleController.php
public function show($id)
{
    return view('sample.show', ['result' => Sample::findOrFail($id)]);
}

Sample::findOrFail($id) は指定IDのデータが見つからなかった場合は404を返す。

編集

入力フォーム

// resources/views/sample/edit.blade.php
<form method="post" action="{{ route('sample.update', [$result->id]) }}">
    {!! csrf_field() !!}
    {{ method_field('PUT') }}
    <div class="form-group">
        <label for="name" class="col-sm-2 control-label">名前*</label>
        <div class="col-sm-10">
            <input type="text" name="name" id="name" class="form-control" value="{{ old('name', $result->name) }}">
        </div>
    </div>
    <div class="form-group">
        <label for="email" class="col-sm-2 control-label">メールアドレス*</label>
        <div class="col-sm-10">
            <input type="text" name="email" id="email" class="form-control" value="{{ old('email', $result->email) }}">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <input type="submit" value="編集" class="btn">
        </div>
    </div>
</form>

{{ method_field('PUT') }} でPUTメソッドとして扱うことができるようにしている。また、old の第2引数にDBから取得した値を初期値として設定している。

コントローラー

// app/Http/Controllers/SampleController.php
public function edit($id)
{
    return view('sample.edit', ['result' => Sample::findOrFail($id)]);
}

public function update(SampleRequest $request, $id)
{
    $sample = Sample::findOrFail($id);

    $sample->name = $request->input('name');
    $sample->email = $request->input('email');

    if ($sample->save()) {
        $request->session()->flash('success', '編集しました。');
    } else {
        $request->session()->flash('error', '編集に失敗しました。');
    }

    return redirect('sample');
}

バリデーションは新規作成と同様。

削除

削除フォーム

一覧画面を参照。

コントローラー

// app/Http/Controllers/SampleController.php
public function destroy(Request $request, $id)
{
    $sample = Sample::findOrFail($id);

    if ($sample->delete()) {
        $request->session()->flash('success', '削除しました。');
    } else {
        $request->session()->flash('error', '削除に失敗しました。');
    }

    return redirect('sample');
}

説明は省略。

テスト

テストはまた別記事にまとめたい。 => 書いた