20191008のlaravelに関する記事は7件です。

LaravelでCRUD

どこにでもある記事だけどね。基本は押さえておかないと。

前提

自分で勝手に作ったadminテーブルに対してCRUDを作成する。
テーブルは下記の記事で作ったやつ。
LarabelでDBにテストデータを仕込む

ここまでに作ったものをまとめると、
App/Admin.php
database/Factories/AdminFactory.php
database/migrations/2019_09_21_104722_create_admin_table.php
database/seeds/AdminTablesSeeder.php

テストデータを作るところだけだね。
このテーブルをCRUDで一式やれるようにしてみるよ。

何はともあれやること

CRUDの何を作るにしても、Controllerを作らないことには話にならない。
例によってartisanでControllerを作ってみる。

$php artisan make:controller AdminController --resource

--resourceをつけると、CRUDに必要なfunctionをあらかじめ入れた形でソースを生成してくれる。

AdminController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AdminController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

それぞれのfunctionについてはlaravelマニュアル見たほうがわかりやすいかも。

URI Action route 内容
GET /admin index admin.index 一覧
GET /admin/create create admin.create 追加
POST /admin store admin.store 追加情報の保存
GET /admin/{id} show admin.show 表示
GET /admin/{id}/edit edit admin.edit 編集
PUT/PATCH /admin/{id} update admin.update 編集情報の更新
DELETE /admin/{id} destroy admin.destroy 削除

一覧表示してみる

まずは一覧表示から。
作らなくちゃいけないものは、以下の通り。
・routes/web.phpにルートを追加する。
・一覧表示画面を作る(/admin/index.blade.php)。
・AdminController.phpのfunction indexに処理を追加する。
では、順番に。

ルートを追加

/admin/indexへのルートを追加する。
--resourceをつけて生成したControllerのルートは、1行で全部設定できる。

web.php
Route::resource('admin', 'adminController');

こうしたときは、画面からジャンプ先を指定するrouteの設定内容は、前に書いた表の「Route」の内容になります。
indexに飛ばいしたいときは、「admin.index」を指定します。

書き方例
<a href="{{ route('admin.index') }}">Admin</a>

一覧画面を作る

とりあえず画面デザインをそろえるため、Login画面を基にして一覧画面を作ってみた。
Login用の項目などを削除して、代わりに一覧表示用のテーブルを追加しています。

index.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Admin Index</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    {{--成功時のメッセージ--}}
                    @if (session('success'))
                    <div class="alert alert-success">{{ session('success') }}</div>
                    @endif
                    {{-- エラーメッセージ --}}
                    @if ($errors->any())
                        <div class="alert alert-danger">
                        <ul>
                            @foreach ($errors->all() as $error)
                                <li>{{ $error }}</li>
                            @endforeach
                        </ul>
                        </div>
                    @endif
                    // この辺から追加
                    <div class="table-resopnsive">
                        <table class="table table-striped">
                            <thead>
                                <tr>
                                    <th>{{__('ID')}}</th>
                                    <th>{{__('admin_code')}}</th>
                                    <th>{{__('name')}}</th>
                                    <th>{{__('role')}}</th>
                                </tr>
                            </thead>
                            <tbody>
                                @if(isset($admins))  // $adminデータ存在チェック
                                    @foreach ($admins as $admin)  // テーブル作成
                                        <tr>
                                            <td>{{ $admin->id }}</td>
                                            <td>{{ $admin->admin_code }}</td>
                                            <td>{{ $admin->name }}</td>
                                            <td>{{ $admin->role}}</td>
                                        </tr>
                                    @endforeach
                                @endif
                            </tbody>
                        </table>
                    // この辺まで追加
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

とりあえず単に一覧を表示するだけの画面です。
@ifで$adminsに値が入っているかチェックしています。
@foreachで取得したデータをすべてテーブルに設定しています。

indexに処理を追加する

今度はadmincontroller.phpのindexにデータ取得処理を追加します。

admincontroller.php
public function index()
{
    $admins = \App\Admin::all();
    return view('admin/index', compact('admins'));
}

adminテーブルはEloquentを使っているので、allで全件抽出することができる。
全件取得したインスタンスをviewに渡してあげれば、bladeの中で一覧表を作ってくれる。

とりあえず実行

上記までの処理を実装すると、こんな画面が表示される。はず。

image.png

ちなみにデータはシーダーで作りました。

次はcreate…の前に

今回のadminに関する処理は、一覧画面から各機能へ遷移するようにします。今決めました。
追加→「追加」ボタンクリック→admin.create→admin.store
詳細→IDをクリック→admin.show
変更→詳細画面で「変更」ボタンをクリック→admin.edit→admin.update
削除→詳細画面で「削除」ボタンをクリック→admin.destroy
なので、createの処理を作る前に、一覧画面に「追加」ボタンを追加します。

menu.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Admin Index</div>

                <div class="card-body">

                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif
                    {{--成功時のメッセージ--}}
                    @if (session('success'))
                    <div class="alert alert-success">{{ session('success') }}</div>
                    @endif
                    {{-- エラーメッセージ --}}
                    @if ($errors->any())
                        <div class="alert alert-danger">
                        <ul>
                            @foreach ($errors->all() as $error)
                                <li>{{ $error }}</li>
                            @endforeach
                        </ul>
                        </div>
                    @endif

                    // ここから追加
                    <button type="button" class="btn btn-primary" onclick="location.href='{{ route('admin.create') }}'">
                        {{ __('追加') }}
                    </button>
                    // ここまで追加

                    <div class="table-resopnsive">
                        <table class="table table-striped">
                            <thead>
                                <tr>
                                    <th>{{__('ID')}}</th>
                                    <th>{{__('admin_code')}}</th>
                                    <th>{{__('name')}}</th>
                                    <th>{{__('role')}}</th>
                                </tr>
                            </thead>
                            <tbody>
                                @if(isset($admins))
                                    @foreach ($admins as $admin)
                                        <tr>
                                            <td>{{ $admin->id }}</td>
                                            <td>{{ $admin->admin_code }}</td>
                                            <td>{{ $admin->name }}</td>
                                            <td>{{ $admin->role}}</td>
                                        </tr>
                                    @endforeach
                                @endif
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

とりあえず追加ボタンだけ追加。

image.png

createとstoreを作る

createはGET、storeはPOSTで送信される。てことは、createは参照だけ、storeで更新ということになります。

controllerのcreateに処理を追加する

createで行うのは登録画面の表示だけなので、controllerには画面表示の処理だけ記述します。

AdminController.php
// createのところだけ抜粋
public function create()
{
    return view('admin.create');
}

createの画面を作ってみる

表示する画面は下記のように作ってみた。基本/auth/register.blade.phpをぱくって作ってます。

create.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Admin Add</div>
                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif
                    {{--成功時のメッセージ--}}
                    @if (session('success'))
                    <div class="alert alert-success">{{ session('success') }}</div>
                    @endif
                    {{-- エラーメッセージ --}}
                    @if ($errors->any())
                        <div class="alert alert-danger">
                        <ul>
                            @foreach ($errors->all() as $error)
                                <li>{{ $error }}</li>
                            @endforeach
                        </ul>
                        </div>
                    @endif

                    <form action="{{ route('admin.store') }}" method="POST">
                        @csrf
                        <div class="form-group row">
                            <label for="admin_code" class="col-md-4 col-form-label text-md-right">{{ __('Admin Code') }}</label>
                            <div class="col-md-6">
                                <input id="admin_code" type="text" class="form-control" name="admin_code" value="{{ old('admin_code') }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="role" class="col-md-4 col-form-label text-md-right">{{ __('Role') }}</label>
                            <div class="col-md-6">
                                <input id="role" type="text" class="form-control" name="role" value="{{ old('role') }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password">
                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation">
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary" name='action' value='add'>
                                    {{ __('追加') }}
                                </button>
                                <button type="submit" class="btn btn-primary" name='action' value='back'>
                                    {{ __('戻る') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

エラーメッセージが出せる仕組みになっているが、今はまだvalidationしてないので何の役にも立ってません。そのうち何とかするから、待っとけ。
画面はこんな感じで表示される。

image.png

追加ボタンを押したら入力データを追加、戻るボタンを押したら一覧画面に戻るようにする。
これは、Formタグのaction属性でadmin.storeに遷移するよう設定している。そのため追加を押そうが戻るを押そうがすべてAdminControllerのstoreに飛ばされる。

controllerのstoreに処理を追加する

createで入力されたものに対する処理を行うのがstoreという位置づけのようであります。
なのでvalidateしたり確認画面出したりテーブル更新したり、という処理は基本的にすべてstoreで行うことになります。
今回はとりあえずテーブルへのinsertだけしてみる。

AdminController.php
// storeのところだけ抜粋
public function store(Request $request)
{
    if($request->action === 'back') {
        return redirect()->route('admin.index');
    } else {
        $admin = new Admin;
        $admin->admin_code = $request->admin_code;
        $admin->name = $request->name;
        $admin->role = $request->role;
        $admin->password = Hash::make($request->password);
        $admin->save();
        return redirect()->route('admin.index');
    }
}

最初に押されたボタンの判定を行い、「戻る」ボタンの場合はindexにリダイレクトしています。
「戻る」ボタンが押されたのではない場合、画面に入力された内容をテーブルにセットしてinsertします。
テーブルにデータを追加したら、indexにリダイレクトします。

詳細を表示する

一覧画面のIDにリンクを張って、IDをクリックしたらそのIDの内容を詳細画面に表示します。
詳細画面に「変更」ボタンを追加して、「変更」ボタンが押されたら編集画面を開くようにします。
まずは詳細画面を表示させます。

一覧画面にリンクを追加する

IDにリンクを追加する。

index.blade.php
//データ表示部分を抜粋
<tbody>
    @if(isset($admins))
        @foreach ($admins as $admin)
            <tr>
                <td><a href="/admin/{{ $admin->id }}">{{ $admin->id }}</a></td>
                <td>{{ $admin->admin_code }}</td>
                <td>{{ $admin->name }}</td>
                <td>{{ $admin->role}}</td>
            </tr>
        @endforeach
    @endif
</tbody>

controllerのshowに処理を追加する

一覧でクリックしたIDを受け取ってadminテーブルを検索し、その内容を表示する。
書くのはこれだけ。

AdminController.php
public function show($id)
{
    $admins = \App\Admin::find($id);
    return view('admin.show', compact('admins'));
}

画面はこんな感じで。

show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Admin Add</div>
                <div class="card-body">
                    @csrf
                    <div class="form-group row">
                        <label for="id" class="col-md-4 col-form-label text-md-right">{{ __('ID') }}</label>
                        <div class="col-md-6 input-group-text">
                            {{ $admins->id }}
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="admin_code" class="col-md-4 col-form-label text-md-right">{{ __('Admin Code') }}</label>
                        <div class="col-md-6 input-group-text">
                            {{ $admins->admin_code }}
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
                        <div class="col-md-6 input-group-text">
                            {{ $admins->name }}
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="role" class="col-md-4 col-form-label text-md-right">{{ __('Role') }}</label>
                        <div class="col-md-6 input-group-text">
                            {{ $admins->role }}
                        </div>
                    </div>
                    <div class="form-group row mb-0">
                        <div class="col-md-6 offset-md-4">
                            <button type="button" class="btn btn-primary" onclick="location.href='{{ route('admin.edit', $admins->id) }}'">
                                {{ __('変更') }}
                            </button>
                            <button type="button" class="btn btn-primary" onclick="history.back()">
                                {{ __('戻る') }}
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

一覧から詳細画面を出すのはこんな感じ。

image.png

IDをクリックすると。

image.png

詳細画面が表示される。

editとupdateを作る

editとupdateはcreateとstoreの関係と同じで、editで変更用の画面を表示して、updateで更新をする。
変更画面は、さっき作った詳細画面にこっそり仕込んだ変更ボタンをクリックして遷移することにしていた。formのactionにeditへのリンクを書いておいたので、遡って見つけてくれ。

Controllerのeditに処理を追加する

今回の処理では詳細画面から変更画面へ遷移するので、直前の処理でテーブル検索しているから、それを持ってきてもいいんだけれど、resourceで作成したひな形では$idを引数にしているので、そのままIDを渡すことにする。
なので、渡されたIDで再度テーブルを検索して、その内容を変更画面に表示させる。

AdminController.php
public function edit($id)
{
    $admins = \App\Admin::find($id);
    return view('admin.edit', compact('admins'));
}

画面はこんな風に。
resourceでrouteを作成すると、updateはmethodがPUTになっているが、HTMLではPUTなんて指定できないので、FormのmethodではPOSTを指定しておき、Formの下に「@method('PUT')」と書いてhiddenの_methodにPUTを設定する。

edit.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Admin Edit</div>
                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif
                    {{--成功時のメッセージ--}}
                    @if (session('success'))
                    <div class="alert alert-success">{{ session('success') }}</div>
                    @endif
                    {{-- エラーメッセージ --}}
                    @if ($errors->any())
                        <div class="alert alert-danger">
                        <ul>
                            @foreach ($errors->all() as $error)
                                <li>{{ $error }}</li>
                            @endforeach
                        </ul>
                        </div>
                    @endif
                    <form action="/admin/{{ $admins->id }}" method="POST">
                        @csrf
                        @method('PUT')
                        <div class="form-group row">
                            <label for="id" class="col-md-4 col-form-label text-md-right">{{ __('ID') }}</label>
                            <div class="col-md-6">
                                <input id="id" class="input-group-text text-md-left" type="text" name="id" value="{{ old('$admins->id', $admins->id) }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="admin_code" class="col-md-4 col-form-label text-md-right">{{ __('Admin Code') }}</label>
                            <div class="col-md-6">
                                <input id="admin_code" type="text" class="form-control" name="admin_code" value="{{ old('admin_code',$admins->admin_code) }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ old('name',$admins->name) }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="role" class="col-md-4 col-form-label text-md-right">{{ __('Role') }}</label>
                            <div class="col-md-6">
                                <input id="role" type="text" class="form-control" name="role" value="{{ old('role',$admins->role) }}">
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password">
                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation">
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary" name='action' value='edit'>
                                    {{ __('変更') }}
                                </button>
                                <button type="submit" class="btn btn-primary" name='action' value='back'>
                                    {{ __('戻る') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

実際の画面はこんな風に表示される。
cssとかデフォルトのままなので、結構苦し紛れだ。

image.png

Controllerのupdateに処理を追加する

中身はstoreに似ているが、追加ではなく更新なので、またテーブル読んじまった。
ここら辺はなんかできそうなので、各自宿題で考えておくように。
更新はsaveでできる。キーがなければinsert、存在したらupdateになる。
あと、passwordはHash化して保存しています。

AdminController.php
public function update(Request $request, $id)
{
    if($request->action === 'back') {
        return redirect()->route('admin.index');
    } else {
        $admin = \App\Admin::find($id);
        $admin->admin_code = $request->admin_code;
        $admin->name = $request->name;
        $admin->role = $request->role;
        $admin->password = Hash::make($request->password);
        $admin->save();
        return redirect()->route('admin.index');
    }
}

最後にDestroy

最後に削除を追加する。

Controllerのdestroyに処理を追加する

AdminController.php
public function destroy($id)
{
    $admins = \App\Admin::find($id);
    $admins->delete();
    return redirect()->route('admin.index');
}

削除ボタンは詳細画面に追加する。
DestroyはmethodにDELETEを指定しなくてはならないので、formでPOST指定した後、@methodでDELETEを指定する。
こんな感じ。

show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Admin Add</div>

                <div class="card-body">
                    @csrf
                    <div class="form-group row">
                        <label for="id" class="col-md-4 col-form-label text-md-right">{{ __('ID') }}</label>

                        <div class="col-md-6 input-group-text">
                            {{ $admins->id }}
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="admin_code" class="col-md-4 col-form-label text-md-right">{{ __('Admin Code') }}</label>

                        <div class="col-md-6 input-group-text">
                            {{ $admins->admin_code }}
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                        <div class="col-md-6 input-group-text">
                            {{ $admins->name }}
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="role" class="col-md-4 col-form-label text-md-right">{{ __('Role') }}</label>

                        <div class="col-md-6 input-group-text">
                            {{ $admins->role }}
                        </div>
                    </div>

                    <div class="form-group row mb-0">
                        <div class="col-md-6 offset-md-4">
                            <button type="button" class="btn btn-primary" onclick="location.href='{{ route('admin.edit', $admins->id) }}'">
                                {{ __('変更') }}
                            </button>
                            <form style="display:inline" action="{{ route('admin.destroy', $admins->id) }}" method="post">
                                @csrf
                                @method('DELETE')
                                <button type="submit" class="btn btn-danger">
                                    {{ __('削除') }}
                                </button>
                            </form>
                            <button type="button" class="btn btn-primary" onclick="history.back()">
                                {{ __('戻る') }}
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

これでとりあえずCRUDが実装できました。
validationが未実装とか、確認画面出してないとか、まだまだ更新するところはいろいろありますが、まずは一段落。
今日のところはここまで。アディオス、アミーゴ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel(5.5)公式ドキュメントにないassertDatabaseHasのDBの指定方法

要約

公式ドキュメント見て載ってなければソースコード見ろ。

背景

Laravelで複数のデータベースを跨ぐテストを実施する際のトランザクションの設定
で当然DBを指定してassertDatabaseHasをしたいが、DBの指定方法が公式ドキュメントにも載ってない。日本語英語も両方とも。

ソースコードを見ると

<?php

namespace Illuminate\Foundation\Testing\Concerns;

use Illuminate\Foundation\Testing\Constraints\HasInDatabase;
use PHPUnit\Framework\Constraint\LogicalNot as ReverseConstraint;
use Illuminate\Foundation\Testing\Constraints\SoftDeletedInDatabase;

trait InteractsWithDatabase
{
    /**
     * Assert that a given where condition exists in the database.
     *
     * @param  string  $table
     * @param  array  $data
     * @param  string  $connection
     * @return $this
     */
    protected function assertDatabaseHas($table, array $data, $connection = null)
    {
        $this->assertThat(
            $table, new HasInDatabase($this->getConnection($connection), $data)
        );

        return $this;
    }

こんな記述が...

具体的にはPHPUnitでこう書けば良い。

$this->assertDatabaseHas('users', [
    'name' => 'JMac',
    'email' => 'jmac@example.com'
], 'mysql2');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【コピペ用】laravelで都道府県のバリデーション

都道府県のバリデーション

作っているうちにどこかでコピペできたら便利だな、と思って記載しました。

Laravelで都道府県を入力するフォームで用いるための
バリデーションロジックです。
バリデーション自体の説明は割愛しますが、
特定の値のみ受け付ける場合にはinを用います。

県あり

$request->validate([
    'prefecture' => 'required|in:北海道,青森県,岩手県,宮城県,秋田県,山形県,福島県,茨城県,栃木県,群馬県,埼玉県,千葉県,東京都,神奈川県,新潟県,富山県,石川県,福井県,山梨県,長野県,岐阜県,静岡県,愛知県,三重県,滋賀県,京都府,大阪府,兵庫県,奈良県,和歌山県,鳥取県,島根県,岡山県,広島県,山口県,徳島県,香川県,愛媛県,高知県,福岡県,佐賀県,長崎県,熊本県,大分県,宮崎県,鹿児島県,沖縄県',
]);

県なし

$request->validate([
    'prefecture' => 'required|in:北海道,青森,岩手,宮城,秋田,山形,福島,茨城,栃木,群馬,埼玉,千葉,東京,神奈川,新潟,富山,石川,福井,山梨,長野,岐阜,静岡,愛知,三重,滋賀,京都,大阪,兵庫,奈良,和歌山,鳥取,島根,岡山,広島,山口,徳島,香川,愛媛,高知,福岡,佐賀,長崎,熊本,大分,宮崎,鹿児島,沖縄',
]);

参考

バリデーションについて公式ドキュメント:
https://laravel.com/docs/5.8/validation

都道府県引用元:
https://www.start-point.net/maps/tool/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Voyagerインストールでつまづいた件

Laravelに、管理画面パッケージである「Voyager」を導入する際のつまづき解消法をかく。

公式ダウンロード方法

https://voyager-docs.devdojo.com/getting-started/installation

遭遇したエラーはこちら

スクリーンショット 2019-10-08 14.46.26.png

「Voyagerのcontrollerがありません」と言われている。

分析

Voyagerのcontrollerはデフォルトでは外部のを使っている?(自分のファイル内にない)ので、
自分のファイル内にVoyagerのcontrollerを支配下におく設定をすれば良いと仮説を立てググってみた。

解決方法

config/voyager.php
    'controllers' => [
     //コメントアウト or 削除
        //'namespace' => 'TCG\\Voyager\\Http\\Controllers',

     //コントローラーの配置先を自分のファイルに変える
        'namespace' => 'App\\Http\\Controllers\\Voyager', 
    ],
ターミナル
$ php artisan voyager:controllers
Published Voyager controllers! //これが出力されればOK

ターミナルでphp artisan voyager:controllersがうまくいかなかった場合、

ターミナル
$ php artisan voyager:controllers

上記のコマンドで作成することもできる。

/adminにアクセスし、以下のような画面へ遷移できたらOK!

スクリーンショット 2019-10-08 15.00.04.png
スクリーンショット 2019-10-08 15.04.51.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel サービスプロバイダ

サービスプロバイダとは

  • Laravelのサービスコンテナへのバインド処理で利用する機能。
  • FWやアプリケーションに含まれるサービス(機能)の初期処理を行う。
  • Laravelのライフサイクルでは、ビジネスロジックが実行される前に、サービスプロバイダのメソッドが呼ばれる。

役割

  • サービスコンテナへのバインド
  • イベントリスナーやミドルウェア、ルーティングの登録
  • 外部コンポーネントを組み込む

基本的な動作

  • Laravelの初期処理で、各サービスプロバイダのregisterメソッドが実行される。
  • registerメソッドでは、サービスコンテナへのバインドのみ行う。(メソッドの実行タイミングでは、サービスコンテナから他の機能のインスタンスを取得する処理を実行できないため。)
  • すべてのregister処理が終わると、bootメソッドが呼ばれる。
  • registerメソッドの実装は必須、bootメソッドは任意。

deferプロパティによる遅延実行

  • deferプロパティをtrueに設定することで、アプリケーション起動時のregisterメソッドの実行を遅らせることができる(デフォルトはfalse)。
  • この場合、registerメソッドの実行タイミングを指定するため、providesメソッドもしくはwhenメソッドで指定する必要がある。

providesメソッド

  • サービスコンテナで解決する文字列を指定する。
  • 文字列の解決をサービスコンテナに依頼したタイミングで、サービスプロバイダのregisterメソッドが呼ばれ、解決が行われる。

whenメソッド

  • イベントを指定する。
  • 対象イベント名を配列で指定すると、リスナーが登録され、その中でサービスプロバイダのregisterメソッドが実行される。
  • 設定したイベントが発行されると、このリスナーが発動して、registerメソッドが実行される。

provideswhenいずれの場合も、サービスコンテナでの解決やイベントの発行がアプリケーション内で実行されるまで、インスタンス登録は行われない。

コード例

class SampleServiceProvider extends ServiceProvider
{
    protected $defer = true;

    public function register()
    {
        $this->app->singleton('foo', function ($app) {
            return new Foo($app);
        });
        $this->app->singleton('bar', function ($app) {
            return new Bar($app);
        });
        $this->app->singleton('buzz', function ($app) {
            return new Buzz($app);
        });
    }

    public function provides()
    {
        return [
            'foo', 'bar', 'buzz'
        ];
    }
}

上記の例では、サービスプロバイダのprovidesメソッドで、文字列を要素とした配列を返している。ビジネスロジックでfooなどのクラス名をサービスコンテナで解決すると、registerメソッドが実行され、ロジック側にインスタンスが返される。

参考図書

PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 6 で管理者だけがユーザーを追加する系の仕組みに認証をカスタマイズする

歳とともにスポーツの上達がガンガン鈍っていることに絶賛絶望中のまついです。

プログラミングはある程度順調に成長していることがせめてもの救いです。

さて、タイトルにもある通り、Laravel の認証をカスタマイズして、管理者だけがユーザーを追加する系のウェブアプリを作る方法をまとめてみました。

なるべくシンプルに、かつ Laravel 6 に対応した方法を考えました。

要件

管理者だけがユーザーを追加できるという状況を作るために、標準状態では困る部分に絞ってリストアップ。

  • ユーザー登録ページには管理者だけがアクセス可能
  • 管理者もユーザーのマイページ的なものを見る必要がある
  • ユーザー作成後に自動ログインされるのは困る
  • ログイン中のユーザーを適切にリダイレクトしたい

こんなところでしょうか。

コード

というわけでコードを先に見ていただきましょう。

web.php
Route::get('/', function () {
    return view('welcome');
});

Route::view('login', 'auth.login')->name('login');

Route::group(['middleware' => ['auth', 'can:admin']], function () {
    Route::get('admin', 'GateController@admin');
    Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
    Route::post('register', 'Auth\RegisterController@register')->name('register');
});

Route::group(['middleware' => ['auth', 'can:user']], function () {
    Route::redirect('/user', '/login', 301);
    Route::get('user/{user}', 'GateController@user');
});

Auth::routes(['register' => false]);

まずはルーティング用のファイルです。

アクセス制限の方法としてはごく普通のコードですね。

ポイントは Auth::routes の引数でユーザー登録のルーティング無効化。登録時のルーティングをカスタマイズするのでこれだけ記入する必要があります。

次に、コントローラーを見ていきましょう。

web.php に書いたとおり、GateController と RegisterController を呼び出しています。

GateController は新規に作成します。

GateController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class GateController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function admin()
    {
        return view('home');
    }

    public function user() {
        return view('home');
    }
}

認証後の動きをここで定義しますが、今回は管理者、ユーザーともに home.blade.php を表示させています。

このあたりは要件によって変えてください。

次に、RegisterController を見てみましょう。

Auth/RegisterController.php
protected $redirectTo = '/login';

protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
        'role' => $data['role'] ?? 5,
    ]);
}

protected function register(Request $request, $user) {
    $this->validator($request->all())->validate();

    event(new Registered($user = $this->create($request->all())));

    // $this->guard()->login($user);

    return $this->registered($request, $user)
                    ?: redirect($this->redirectPath());
}

全部載せると長いので一部抜粋。

まずは $redirectTo = '/login' でユーザー作成後のリダイレクト先を指定します。リダイレクト先をログインページにしている理由は、後ほど掲載するログインページで認証済みの場合の処理に関係してくるのです。

次に、create メソッドに Role を追加。今回はデフォルト値を設定してあります。

そして、register() メソッド。これは Illuminate/Foundation/Auth/RegistersUsers.php の register() メソッドを上書き。

RegisterUsers.php には registered() という空のメソッドがあって、いかにも上書きしろと誘ってきますが、今回はスルー。

内容的にはメソッドをコピペして、$this->guard()->login($user); をコメントアウトしています。

動き的にはこのログイン処理、registered() にあるべきな気がするんですが、どうなんでしょうね。。。

続きましては、AuthServiceProvider.php です。

Providers\AuthServiceProvider.php
const ADMIN_ROLE = 10;
const USER_ROLE = 5;

public function register()
{
    //
}

public function boot()
{
    $this->registerPolicies();

    Gate::define('admin', function ($user) {
        return $user->role == self::ADMIN_ROLE;
    });

    Gate::define('user', function ($user) {
        return $user->role == self::USER_ROLE;
    });
}

最初にRoleの値を決めて、定数に入れます。

今回、管理者を10、ユーザーを5にしていますが、このへんはお好きなようにどうぞ。

そして、Gateの定義をするのですが、今回は管理者とユーザーの2種類あるのでそれぞれ定義。

ここは特にややこしいこともないですね。

さあ、次で最後になります。Middleware にある RedirectIfAuthenticated.php。こいつを見ていきましょう。

Middleware/RedirectIfAuthenticated.php
const ADMIN_ROLE = 10;
public function handle($request, Closure $next, $guard = null)
{
    if (Auth::guard($guard)->check()) {
        $redirect = Auth::user()->role == ADMIN_ROLE ? 'admin' : 'user/'.Auth::user()->name;
        return redirect('/'.$redirect);
    }

    return $next($request);
}

訪問者が認証済みの場合の動きを定義しています。管理者の場合は /admin それ以外は /user/{username} にリダイレクトといった感じ。

Roleの値はいくつかの場所で使うので、コンフィグかなにかに書いておいたほうがいいですね。

そんなわけで、ネットで探してもドンピシャのものが見つからなかったので自分用メモとして書いてみました。

こういう構成は多分、それほど需要が無いか、開発者が独自で実装していて表に情報が出ていないかということなんでしょうかね。

Laravelで似たようなことをしたい人のお役に立てたら嬉しいです。

では。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 6で一覧ページに検索窓とページ行数指定を付ける

 Laravel 6でindex()コントローラとビュー(Blade)を使ってレコード一覧を作るときに、Laravel標準ページネーションを使いつつ、キーワードによる絞り込み、1ページの表示数を指定できるようにする方法を紹介します。

 指定した条件は、URLのクエリーパラメータに以下のような形で明示されるようにします。これにより、ユーザーがリンクをほかのユーザーに共有した時に、そのユーザーも同じ画面が見られます。

/customers?keyword=a&perpage=10&page=3

image.png

今回はCustomerモデルの一覧画面という設定で見ていきましょう。

コントローラ

まずはコントローラ。

CustomerController.php
    public function index(Request $request)
    {
        $query = Customer::query();

        $keyword = $request->input('keyword');
        if (!empty($keyword)) {
            $query->where('name', 'like', '%' . $keyword . '%');
        }

        $perpage = $request->input('perpage', 10);

        //paginate
        $customers = $query->paginate($perpage);
        return view('customers.index', ['customers' => $customers->appends($request->except('page')), 'request'=>$request->except('page')]);
    }

順に見ていきます。

検索キーワードと表示行数の入力をコントローラで受け取るため、Requestオブジェクトをパラメータに追加します。

public function index(Request $request)

次に検索条件を追加できるようにCustomerモデルからクエリービルダーを取得します。

        $query = Customer::query();

検索キーワードをGetパラメータから取得し、指定があればwhere句を追加します。

        $keyword = $request->input('keyword');
        if (!empty($keyword)) {
            $query->where('name', 'like', '%' . $keyword . '%');
        }

また、表示行数もGetパラメータから取得し、指定があればその数値、なければ10行とします。
Requestオブジェクトのinputメソッドに第2パラメータを指定すると、規定値として処理してくれます。

        $perpage = $request->input('perpage', 10);

指定条件で絞って、さらにページ処理(Pagination)を施した結果のCustomer一覧を取得します。

        $customers = $query->paginate($perpage);

最後にcustomers.indexというblade(ビュー)に、絞り込んだ$customersを渡します。

        return view('customers.index', ['customers' => $customers->appends($request->except('page')), 'request'=>$request->except('page')]);

このとき、customerのPaginationがpage以外のGetパラメータを適切に取り扱えるよう、以下のようにパラメータを渡しています。

'customers' => $customers->appends($request->except('page'))

さらにbladeに、手動でGetパラメータ全体(Page以外)を渡しています。

'request'=>$request->except('page')]

 ビュー

まずPaginationリンク。これは標準のまま。

index.blade.php
{{ $customers->links() }}

次に検索ボックス。

index.blade.php
<form method="get">
    @foreach ($request as $key=>$value)
        @if ($key!="keyword")
            <input type="hidden" name="{{$key}}" value="{{$value}}" />
        @endif
    @endforeach
    <input type="text" class="form-control" name="keyword" placeholder="Name" value="{{$request["keyword"] ?? ""}}">
</form>

自分のパラメータであるkeyword以外についてはhiddenで保持します。ただしpageはコントローラ側で排除したので、ここに保持されません。つまり、キーワード検索を実施すると必ず1ページ目に戻ることになります。
さらに、inputボックスの値は{{$request["keyword"] ?? ""}}とし、キーワードが既に指定されていればそれ、なければ空文字列とします。なお、??はPHP7以降で導入されたnull合体演算子という便利な三項演算子です。

同じように1ページの行数指定ドロップダウンを作ります。

index.blade.php
<form method="get">
    @foreach ($request as $key=>$value)
        @if ($key!="perpage")
            <input type="hidden" name="{{$key}}" value="{{$value}}" />
        @endif
    @endforeach
    <select class="custom-select" name="perpage" onchange="this.form.submit()">
        <option value="10" {{($request["perpage"] ?? "")==10?"selected":""}}>10</option>
        <option value="20" {{($request["perpage"] ?? "")==20?"selected":""}}>20</option>
        <option value="50" {{($request["perpage"] ?? "")==50?"selected":""}}>50</option>
        <option value="100" {{($request["perpage"] ?? "")==100?"selected":""}}>100</option>
    </select>
</form>

形は違いますがやってることは上と同じです。
本来なら行数指定がまだの場合、デフォルトの選択が何になるか明示すべきですが、デフォルトの選択肢が一番上に来ているため、初期状態の"selected"指定はなし=一番上になる、ということにして手抜きしています。

なぜcustomersに格納されているパラメータを使わないのか

 コントローラからビューに渡す$customersには、実はクエリーパラメータが格納されています。しかし、このパラメータは仕様上protectedになっていて、blade内で読むことはできません。そのため、自前で$requestをbladeに渡しているのです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む