- 投稿日:2019-10-08T22:02:24+09:00
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.phpRoute::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.phppublic function index() { $admins = \App\Admin::all(); return view('admin/index', compact('admins')); }adminテーブルはEloquentを使っているので、allで全件抽出することができる。
全件取得したインスタンスをviewに渡してあげれば、bladeの中で一覧表を作ってくれる。とりあえず実行
上記までの処理を実装すると、こんな画面が表示される。はず。
ちなみにデータはシーダーで作りました。
次は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とりあえず追加ボタンだけ追加。
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してないので何の役にも立ってません。そのうち何とかするから、待っとけ。
画面はこんな感じで表示される。
追加ボタンを押したら入力データを追加、戻るボタンを押したら一覧画面に戻るようにする。
これは、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.phppublic 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一覧から詳細画面を出すのはこんな感じ。
IDをクリックすると。
詳細画面が表示される。
editとupdateを作る
editとupdateはcreateとstoreの関係と同じで、editで変更用の画面を表示して、updateで更新をする。
変更画面は、さっき作った詳細画面にこっそり仕込んだ変更ボタンをクリックして遷移することにしていた。formのactionにeditへのリンクを書いておいたので、遡って見つけてくれ。Controllerのeditに処理を追加する
今回の処理では詳細画面から変更画面へ遷移するので、直前の処理でテーブル検索しているから、それを持ってきてもいいんだけれど、resourceで作成したひな形では$idを引数にしているので、そのままIDを渡すことにする。
なので、渡されたIDで再度テーブルを検索して、その内容を変更画面に表示させる。AdminController.phppublic 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とかデフォルトのままなので、結構苦し紛れだ。
Controllerのupdateに処理を追加する
中身はstoreに似ているが、追加ではなく更新なので、またテーブル読んじまった。
ここら辺はなんかできそうなので、各自宿題で考えておくように。
更新はsaveでできる。キーがなければinsert、存在したらupdateになる。
あと、passwordはHash化して保存しています。AdminController.phppublic 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.phppublic 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が未実装とか、確認画面出してないとか、まだまだ更新するところはいろいろありますが、まずは一段落。
今日のところはここまで。アディオス、アミーゴ!
- 投稿日:2019-10-08T16:13:45+09:00
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');
- 投稿日:2019-10-08T15:53:52+09:00
【コピペ用】laravelで都道府県のバリデーション
都道府県のバリデーション
作っているうちにどこかでコピペできたら便利だな、と思って記載しました。
Laravelで都道府県を入力するフォームで用いるための
バリデーションロジックです。
バリデーション自体の説明は割愛しますが、
特定の値のみ受け付ける場合にはin
を用います。県あり
$request->validate([ 'prefecture' => 'required|in:北海道,青森県,岩手県,宮城県,秋田県,山形県,福島県,茨城県,栃木県,群馬県,埼玉県,千葉県,東京都,神奈川県,新潟県,富山県,石川県,福井県,山梨県,長野県,岐阜県,静岡県,愛知県,三重県,滋賀県,京都府,大阪府,兵庫県,奈良県,和歌山県,鳥取県,島根県,岡山県,広島県,山口県,徳島県,香川県,愛媛県,高知県,福岡県,佐賀県,長崎県,熊本県,大分県,宮崎県,鹿児島県,沖縄県', ]);県なし
$request->validate([ 'prefecture' => 'required|in:北海道,青森,岩手,宮城,秋田,山形,福島,茨城,栃木,群馬,埼玉,千葉,東京,神奈川,新潟,富山,石川,福井,山梨,長野,岐阜,静岡,愛知,三重,滋賀,京都,大阪,兵庫,奈良,和歌山,鳥取,島根,岡山,広島,山口,徳島,香川,愛媛,高知,福岡,佐賀,長崎,熊本,大分,宮崎,鹿児島,沖縄', ]);参考
バリデーションについて公式ドキュメント:
https://laravel.com/docs/5.8/validation
- 投稿日:2019-10-08T14:58:21+09:00
Voyagerインストールでつまづいた件
Laravelに、管理画面パッケージである「Voyager」を導入する際のつまづき解消法をかく。
公式ダウンロード方法
https://voyager-docs.devdojo.com/getting-started/installation
遭遇したエラーはこちら
「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-08T11:38:33+09:00
Laravel サービスプロバイダ
サービスプロバイダとは
- Laravelのサービスコンテナへのバインド処理で利用する機能。
- FWやアプリケーションに含まれるサービス(機能)の初期処理を行う。
- Laravelのライフサイクルでは、ビジネスロジックが実行される前に、サービスプロバイダのメソッドが呼ばれる。
役割
- サービスコンテナへのバインド
- イベントリスナーやミドルウェア、ルーティングの登録
- 外部コンポーネントを組み込む
基本的な動作
- Laravelの初期処理で、各サービスプロバイダの
register
メソッドが実行される。register
メソッドでは、サービスコンテナへのバインドのみ行う。(メソッドの実行タイミングでは、サービスコンテナから他の機能のインスタンスを取得する処理を実行できないため。)- すべての
register
処理が終わると、boot
メソッドが呼ばれる。register
メソッドの実装は必須、boot
メソッドは任意。
defer
プロパティによる遅延実行
defer
プロパティをtrue
に設定することで、アプリケーション起動時のregister
メソッドの実行を遅らせることができる(デフォルトはfalse
)。- この場合、
register
メソッドの実行タイミングを指定するため、provides
メソッドもしくはwhen
メソッドで指定する必要がある。providesメソッド
- サービスコンテナで解決する文字列を指定する。
- 文字列の解決をサービスコンテナに依頼したタイミングで、サービスプロバイダの
register
メソッドが呼ばれ、解決が行われる。whenメソッド
- イベントを指定する。
- 対象イベント名を配列で指定すると、リスナーが登録され、その中でサービスプロバイダの
register
メソッドが実行される。- 設定したイベントが発行されると、このリスナーが発動して、
register
メソッドが実行される。※
provides
、when
いずれの場合も、サービスコンテナでの解決やイベントの発行がアプリケーション内で実行されるまで、インスタンス登録は行われない。コード例
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
メソッドが実行され、ロジック側にインスタンスが返される。参考図書
- 投稿日:2019-10-08T10:05:23+09:00
Laravel 6 で管理者だけがユーザーを追加する系の仕組みに認証をカスタマイズする
歳とともにスポーツの上達がガンガン鈍っていることに絶賛絶望中のまついです。
プログラミングはある程度順調に成長していることがせめてもの救いです。
さて、タイトルにもある通り、Laravel の認証をカスタマイズして、管理者だけがユーザーを追加する系のウェブアプリを作る方法をまとめてみました。
なるべくシンプルに、かつ Laravel 6 に対応した方法を考えました。
要件
管理者だけがユーザーを追加できるという状況を作るために、標準状態では困る部分に絞ってリストアップ。
- ユーザー登録ページには管理者だけがアクセス可能
- 管理者もユーザーのマイページ的なものを見る必要がある
- ユーザー作成後に自動ログインされるのは困る
- ログイン中のユーザーを適切にリダイレクトしたい
こんなところでしょうか。
コード
というわけでコードを先に見ていただきましょう。
web.phpRoute::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.phpnamespace 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.phpprotected $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.phpconst 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.phpconst 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で似たようなことをしたい人のお役に立てたら嬉しいです。
では。
- 投稿日:2019-10-08T05:13:47+09:00
Laravel 6で一覧ページに検索窓とページ行数指定を付ける
Laravel 6でindex()コントローラとビュー(Blade)を使ってレコード一覧を作るときに、Laravel標準ページネーションを使いつつ、キーワードによる絞り込み、1ページの表示数を指定できるようにする方法を紹介します。
指定した条件は、URLのクエリーパラメータに以下のような形で明示されるようにします。これにより、ユーザーがリンクをほかのユーザーに共有した時に、そのユーザーも同じ画面が見られます。
/customers?keyword=a&perpage=10&page=3今回は
Customer
モデルの一覧画面という設定で見ていきましょう。コントローラ
まずはコントローラ。
CustomerController.phppublic 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に渡しているのです。