Edited at

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が未実装とか、確認画面出してないとか、まだまだ更新するところはいろいろありますが、まずは一段落。

今日のところはここまで。アディオス、アミーゴ!