LaravelでCRUDシステムを構築する①
下記の記事で作った素のPHPによるユーザー管理のCRUDシステムをLaravelで作り直します。フレームワークを使用することにより、フレームワークの提供する独自のメソッドや関数、コマンド等を覚える必要がありますが、短いコードで素早く開発することが可能になります。
筆者の開発環境
PC:Apple M1 チップ搭載MacBook Air
OS:macOS Sonoma 14.1(23B74)
MAMP:6.8
PHP:8.2.0
Laravel:10.29.0
Laravelのインストール
Laravelのインストールは下記の記事を参考に済ませておいてください。
データベースの作成
データベースの作成がまだの場合はphpMyAdminで作成しておきましょう。MAMPのコントロールパネルの右上にある「WebStart」ボタンをクリックしてください。
ブラウザに下記のページが開きますので「Tools」から「phpMyAdmin」をクリックしてください。
phpMyAdminが起動します。「新規作成」をクリックしてください。
「データベース名」の項目に「laravel_crud」と入力し、その右隣のプルダウンから「utf8mb4_general_ci」を選択し「作成」ボタンをクリックしてください。
下記のように左サイドのデータベース一覧に「laravel_crud」が追加されれば成功です。phpMyAdminページはこの後も使いますので閉じずにそのままにしておいてください。
Laravelのデータベース接続設定を編集します。お使いのエディターでプロジェクトを開き.envというファイルを下記のように編集してください。下記設定はMAMPのデフォルト設定の場合です。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
↓
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel_crud
DB_USERNAME=root
DB_PASSWORD=root
!!!注意!!!
本番環境ではrootユーザーの使用、推測可能なパスワードの使用は絶対に行わないでください!
設定が正しくできたか、下記のコマンドを実行して確かめましょう。ターミナルを起動しプロジェクトフォルダで実行してください。デフォルトのマイグレーションファイルからテーブルが作られます。
php artisan migrate
下記のように表示されれば成功です。
INFO Preparing database.
Creating migration table .................................................................................................... 17ms DONE
INFO Running migrations.
2014_10_12_000000_create_users_table ........................................................................................ 22ms DONE
2014_10_12_100000_create_password_reset_tokens_table ........................................................................ 23ms DONE
2019_08_19_000000_create_failed_jobs_table .................................................................................. 20ms DONE
2019_12_14_000001_create_personal_access_tokens_table ....................................................................... 28ms DONE
念のためphpMyAdminでも確認してみましょう。左サイドのデータベース一覧から「laravel_crud」をクリックし、下記のように5つのテーブルが作られていることを確認してください。
このようにLaravelではマイグレーションという仕組みを使って、テーブルの作成やカラムの追加/削除等をコードとコマンドで行います。
Memberモデルの作成
実はLaravelにはデフォルトでUserモデルとusersテーブルが存在します。そこで今回はユーザーの代わりにメンバー管理のCRUDシステムを作成することとします。
Laravelではモデルを作成するのに専用のコマンドを使用します。そこにオプションを渡すことで関連ファイルを一気に生成することが可能です。下記のコマンドを実行してください。
php artisan make:model Member -mcrR
成功すれば下記のように表示されます。
INFO Model [app/Models/Member.php] created successfully.
INFO Migration [database/migrations/2023_10_28_115202_create_members_table.php] created successfully.
INFO Request [app/Http/Requests/StoreMemberRequest.php] created successfully.
INFO Request [app/Http/Requests/UpdateMemberRequest.php] created successfully.
INFO Controller [app/Http/Controllers/MemberController.php] created successfully.
今回はモデルファイルの他に、
membersテーブルを作成するマイグレーションファイル
バリデーション等を設定するフォームリクエストファイル
CRUD用のメソッドがすでに準備されたコントローラーファイル
が生成されました。
マイグレーションファイルの編集
「database/migrations/2023_10_28_115202_create_members_table.php」をエディターで開いてください。ファイル名の先頭の「2023_10_28_115202」は作成日時ですので、ファイル名が異なると思いますので「create_members_table」というファイル名で探してください。
下記のように編集してください。nameとemailという2つのカラムを持つmembersテーブルを作成します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('members', function (Blueprint $table) {
$table->id();
$table->string('name', 50);
$table->string('email', 100);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('members');
}
};
編集できたら下記のコマンドを実行してください。
php artisan migrate
下記のように表示されれば成功です。
INFO Running migrations.
2023_10_28_115202_create_members_table ...................................................................................... 13ms DONE
phpMyAdminでも確認してみましょう。左サイドのデータベース一覧から「laravel_crud」をクリックし、テーブル一覧に「members」が追加されていることを確認してください。
カラムの確認もしてみましょう。「members」テーブルをクリックし、上部のメニューから「構造」をクリックしてください。下記のようにnameとemailが作成されていれば成功です。
Memberモデルの編集
「app/Models/Member.php」を下記のように編集してください。$fillableというプロパティはここに指定されたカラム名のみが作成時や更新時に有効になるようにします。逆に言えば、ここに指定されていないカラム名は無視されるということになります。マスアサインメントという脆弱性の対策です。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Member extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
];
}
ルーティングの設定
「routes/web.php」というルーティング設定用のファイルを下記のように編集してください。「Route::resource('member', MemberController::class);」という1行を設定するだけでCRUD用のメソッドが自動で生成されます。MemberControllerをuseするのを忘れないようにしてください。
<?php
use App\Http\Controllers\MemberController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::resource('member', MemberController::class);
編集できたら生成されるルーティングを確認してみましょう。下記のコマンドを実行してください。
php artisan route:list --path=member
下記のように表示されます。
GET|HEAD member .......................................................................... member.index › MemberController@index
POST member .......................................................................... member.store › MemberController@store
GET|HEAD member/create ................................................................. member.create › MemberController@create
GET|HEAD member/{member} ................................................................... member.show › MemberController@show
PUT|PATCH member/{member} ............................................................... member.update › MemberController@update
DELETE member/{member} ............................................................. member.destroy › MemberController@destroy
GET|HEAD member/{member}/edit .............................................................. member.edit › MemberController@edit
上から順に
一覧画面
新規登録処理
登録フォームの表示
詳細画面
更新処理
削除処理
編集フォームの表示
となっており、それぞれのURLに対応するコントローラー名とメソッド名が明示されています。「member.index」や「member.create」というのはURLの別名(エイリアス)定義です。後ほど出てくるroute()関数に「member.index」というエイリアスを渡すと「http://localhost:8888/member」というURLを生成してくれます。
レイアウトの構築
各画面の共通パーツはコンポーネントという仕組みを使いレイアウトにまとめます。下記のコマンドを実行しコンポーネントファイルを格納するフォルダを作成してください。
mkdir resources/views/components
下記のコマンドを実行しレイアウト用のビューファイルを作成してください。
touch resources/views/components/layout.blade.php
layout.blade.phpを下記のように編集してください。画面のデザインにBootstrap5を使用しますのでCDNから読み込んでいます。フラッシュメッセージの表示パーツもすでに作っておきます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'メンバー管理' }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col">
@if (session()->has('success'))
<div class="alert alert-success mt-5" role="alert">
{{ session()->get('success') }}
</div>
@endif
{{ $slot }}
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
メンバー登録フォームの作成
それでは準備が整いましたのでCRUD処理を作成していきます。まずは登録処理を作成します。ユーザーの入力値をサーバーにPOSTするフォームから作成していきます。
MemberControllerの編集
createメソッドを下記のように編集してください。ビューファイルを返します。
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('member.create');
}
ビューファイルの作成
下記のコマンドを実行しメンバー管理用のビューファイルを格納するフォルダを作成してください。
mkdir resources/views/member
下記のコマンドを実行しcreate.blade.phpを作成してください。
touch resources/views/member/create.blade.php
create.blade.phpを下記のように編集してください。CSRF対策として「@csrf」を必ず埋め込んでください。バリデーションエラー時にはエラーメッセージを表示し、old()関数によって直前の入力値を表示していることにも注目してください。
<x-layout>
<x-slot:title>
新規登録 | メンバー管理
</x-slot>
<form action="{{ route('member.store') }}" method="POST" class="mt-5 @if($errors->isNotEmpty()) was-validated @endif" novalidate>
@csrf
<div class="mb-3">
<label for="name" class="form-label">名前</label>
<input type="text" id="name" class="form-control" name="name" maxlength="50" value="{{ old('name') }}" required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="email" class="form-label">メールアドレス</label>
<input type="text" id="name" class="form-control" name="email" maxlength="100" value="{{ old('email') }}" required>
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">登録</button>
</form>
</x-layout>
画面イメージは下記の通りです。
メンバー登録処理の作成
MemberControllerの編集
storeメソッドを下記のように編集してください。データベースへ登録し、登録フォームへリダイレクトしています。リダイレクトの直前にフラッシュメッセージの登録も行っています。
/**
* Store a newly created resource in storage.
*/
public function store(StoreMemberRequest $request)
{
Member::create($request->only(['name', 'email']));
return redirect()->route('member.create')->with('success', 'メンバーの登録に成功しました。');
}
フォームリクエストの編集
バリデーションを設定します。「app/Http/Requests/StoreMemberRequest.php」を下記のように編集してください。rules()メソッドに各カラムのバリデーションルールを定義しています。今回は必須項目をチェックする「required」ルールだけ設定します。
authorize()メソッドがtrueを返すようにしてください。attributes()メソッドとmessages()メソッドをそれぞれ追加してください。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreMemberRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required',
'email' => 'required',
];
}
/**
* カラム名を日本語化
*
* @return array
*/
public function attributes(): array
{
return [
'name' => '名前',
'email' => 'メールアドレス',
];
}
/**
* エラーメッセージ
*
* @return array
*/
public function messages(): array
{
return [
'name.required' => ':attributeを入力してください。',
'email.required' => ':attributeを入力してください。',
];
}
}
動作確認
編集できたらhttp://localhost:8888/member/createにアクセスしてください。名前とメールアドレスを入力し、正常に登録されることを確認してください。下記のようにフラッシュメッセージが表示されれば成功です。
phpMyAdminでも確認してみましょう。上部のメニューから「表示」をクリックしてください。下記のようにデータが登録されていればOKです。
バリデーションエラーも試してみましょう。何も入力せずに登録ボタンをクリックしてください。下記のようにエラーメッセージが表示されればOKです。
old()関数が働いて直前の入力値を表示できることも試しておきましょう。名前のみ入力して登録ボタンをクリックしてください。下記のように入力した値が保持されていればOKです。メールアドレスのみ入力のパターンも試せれば尚良いです。
テストが終わったら、もう何件かデータを登録しておきましょう。
メンバー一覧画面の作成
MemberControllerの編集
indexメソッドを下記のように編集してください。登録の新しい順番にメンバーリストを取得しています。viewメソッドの第2引数に配列を渡すことでビューファイルに変数を渡すことができます。
/**
* Display a listing of the resource.
*/
public function index()
{
$members = Member::orderBy('id', 'DESC')->get();
return view('member.index', compact('members'));
}
一覧画面の作成
下記のコマンドを実行してください。
touch resources/views/member/index.blade.php
index.phpを下記のように編集してください。$members配列をforeachで1件ずつ取り出しテーブルに表示しています。ユーザーによって入力された値を表示する時は必ず「{{ }}」で囲んで表示してください!Laravelにおけるクロスサイトスクリプティング(XSS)攻撃へのセキュリティ対策です。
<x-layout>
<x-slot:title>
一覧 | メンバー管理
</x-slot>
<a href="{{ route('member.create') }}" class="btn btn-success mt-5">新規登録</a>
<table class="table table-bordered mt-5">
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
</tr>
</thead>
<tbody>
@foreach ($members as $member)
<tr>
<td>{{ $member->id }}</td>
<td>{{ $member->name }}</td>
<td>{{ $member->email }}</td>
</tr>
@endforeach
</tbody>
</table>
</x-layout>
編集できたらhttp://localhost:8888/memberにアクセスしてください。先ほど登録したデータが一覧で表示されれば成功です。
リダイレクト先の変更
新規登録後のリダイレクト先を一覧画面に修正しましょう。MemberControllerのstoreメソッドを下記のように編集してください。
/**
* Store a newly created resource in storage.
*/
public function store(StoreMemberRequest $request)
{
Member::create($request->only(['name', 'email']));
return redirect()->route('member.index')->with('success', 'メンバーの登録に成功しました。');
}
編集できたらhttp://localhost:8888/member/createにアクセスするか、新規登録ボタンをクリックしてください。データを入力し下記のようにリダイレクトされれば成功です。
新規登録フォームに一覧画面へ「戻る」ボタンをつけましょう。resources/views/member/create.blade.phpを下記のように修正してください。
<x-layout>
(省略)
<a href="{{ route('member.index') }}" class="btn btn-secondary mt-5">戻る</a>
<form action="{{ route('member.store') }}" method="POST" class="mt-5 @if($errors->isNotEmpty()) was-validated @endif" novalidate>
(省略)
</form>
</x-layout>
メンバー編集フォームの作成
MemberControllerの編集
editメソッドを下記のように編集してください。メソッドインジェクションという仕組みにより、URLで指定されたmemberのidから自動的にmemberインスタンスが取得されます。
/**
* Show the form for editing the specified resource.
*/
public function edit(Member $member)
{
return view('member.edit', compact('member'));
}
ビューファイルの作成
下記のコマンドを実行しedit.blade.phpを作成してください。
touch resources/views/member/edit.blade.php
edit.blade.phpを下記のように編集してください。route()関数の第2引数にmemberインスタンスを渡すとURLパラメーターにidをセットすることができます。formはPATCHメソッドをサポートしていないのですが「@method('PATCH')」とすることで擬似的にPATCHメソッドを表現できます。old()関数の第2引数に、oldの値がnullだった場合のデフォルト値を設定できます。ここではデータベースから取得した値をデフォルト値として渡しています。
<x-layout>
<x-slot:title>
編集 | メンバー管理
</x-slot>
<a href="{{ route('member.index') }}" class="btn btn-secondary mt-5">戻る</a>
<form action="{{ route('member.update', $member) }}" method="POST" class="mt-5 @if($errors->isNotEmpty()) was-validated @endif" novalidate>
@csrf
@method('PATCH')
<div class="mb-3">
<label for="name" class="form-label">名前</label>
<input type="text" id="name" class="form-control" name="name" maxlength="50" value="{{ old('name', $member->name) }}" required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="email" class="form-label">メールアドレス</label>
<input type="text" id="name" class="form-control" name="email" maxlength="100" value="{{ old('email', $member->email) }}" required>
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">更新</button>
</form>
</x-layout>
resources/views/member/index.blade.phpを下記のように編集してください。編集画面へのリンクを設置します。
<x-layout>
(省略)
<table class="table table-bordered mt-5">
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
<th>編集</th>
</tr>
</thead>
<tbody>
@foreach ($members as $member)
<tr>
<td>{{ $member->id }}</td>
<td>{{ $member->name }}</td>
<td>{{ $member->email }}</td>
<td>
<a href="{{ route('member.edit', $member) }}" class="btn btn-primary">編集</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</x-layout>
メンバー更新処理の作成
MemberControlllerの編集
updateメソッドを下記のように編集してください。all()メソッドはPOSTされた値を配列で返します。fill()メソッドは$fillableで指定したカラムのみを更新対象に設定します。
/**
* Update the specified resource in storage.
*/
public function update(UpdateMemberRequest $request, Member $member)
{
$member->fill($request->all())->save();
return redirect()->route('member.index')->with('success', 'メンバーの更新に成功しました。');
}
フォームリクエストの編集
バリデーションを設定します。「app/Http/Requests/UpdateMemberRequest.php」を下記のように編集してください。rules()メソッドに各カラムのバリデーションルールを定義しています。今回は必須項目をチェックする「required」ルールだけ設定します。
authorize()メソッドがtrueを返すようにしてください。attributes()メソッドとmessages()メソッドをそれぞれ追加してください。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateMemberRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required',
'email' => 'required',
];
}
/**
* カラム名を日本語化
*
* @return array
*/
public function attributes(): array
{
return [
'name' => '名前',
'email' => 'メールアドレス',
];
}
/**
* エラーメッセージ
*
* @return array
*/
public function messages(): array
{
return [
'name.required' => ':attributeを入力してください。',
'email.required' => ':attributeを入力してください。',
];
}
}
動作確認
編集できたら一覧画面(http://localhost:8888/member/create)から編集フォームにアクセスしてください。データを修正してデータが更新されること確認してください。下記のようにフラッシュメッセージが表示されれば成功です。
バリデーションエラーも確認しましょう。名前を空にして更新ボタンをクリックしてください。下記のようにエラーメッセージが表示されればOKです。メールアドレスも同様に検査できれば尚良いです。
メンバー削除処理の作成
MemberControllerの編集
destroyメソッドを下記のように編集してください。
/**
* Remove the specified resource from storage.
*/
public function destroy(Member $member)
{
$member->delete();
return redirect()->route('member.index')->with('success', 'メンバーの削除に成功しました。');
}
削除フォームの作成
resources/views/member/index.blade.phpを下記のように編集してください。
<x-layout>
(省略)
<table class="table table-bordered mt-5">
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
<th>編集</th>
<th>削除</th>
</tr>
</thead>
<tbody>
@foreach ($members as $member)
<tr>
<td>{{ $member->id }}</td>
<td>{{ $member->name }}</td>
<td>{{ $member->email }}</td>
<td>
<a href="{{ route('member.edit', $member) }}" class="btn btn-primary">編集</a>
</td>
<td>
<form action="{{ route('member.destroy', $member) }}" method="POST">
@csrf
@method('DELETE')
<button class="btn btn-danger">削除</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</x-layout>
動作確認
編集できたら一覧画面(http://localhost:8888/member/create)から削除ボタンをクリックしてください。データが削除されること確認してください。下記のようにフラッシュメッセージが表示されれば成功です。
メンバー詳細画面の作成
MemberControllerの編集
showメソッドを下記のように編集してください。
/**
* Display the specified resource.
*/
public function show(Member $member)
{
return view('member.show', compact('member'));
}
ビューファイルの作成
下記のコマンドを実行しshow.blade.phpを作成してください。
touch resources/views/member/show.blade.php
show.blade.phpを下記のように編集してください。
<x-layout>
<x-slot:title>
詳細 | メンバー管理
</x-slot>
<a href="{{ route('member.index') }}" class="btn btn-secondary mt-5">戻る</a>
<p class="mt-5">ID: {{ $member->id }}</p>
<p>名前: {{ $member->name }}</p>
<p>メールアドレス: {{ $member->email }}</p>
<p>作成日時: {{ $member->created_at }}</p>
<p>更新日時: {{ $member->updated_at }}</p>
</x-layout>
一覧画面の名前をリンカブルに修正します。resources/views/member/index.blade.phpを下記のように修正してください。
<x-layout>
(省略)
<table class="table table-bordered mt-5">
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
<th>編集</th>
<th>削除</th>
</tr>
</thead>
<tbody>
@foreach ($members as $member)
<tr>
<td>{{ $member->id }}</td>
<td>
<a href="{{ route('member.show', $member) }}">{{ $member->name }}</a>
</td>
<td>{{ $member->email }}</td>
<td>
<a href="{{ route('member.edit', $member) }}" class="btn btn-primary">編集</a>
</td>
<td>
<form action="{{ route('member.destroy', $member) }}" method="POST">
@csrf
@method('DELETE')
<button class="btn btn-danger">削除</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</x-layout>
動作確認
編集できたら一覧画面(http://localhost:8888/member)にアクセスし、名前をクリックして詳細画面が表示されることを確認してください。下記のように表示されれば成功です。
404画面の表示
存在しないmemberのID等が渡された時にオリジナルの404ページを表示します。
ビューファイルの作成
下記のコマンドを実行しエラー画面用のビューファイルを格納するフォルダを作成してください。
mkdir resources/views/errors
下記のコマンドを実行し404.blade.phpを作成してください。
touch resources/views/errors/404.blade.php
404.blade.phpを下記のように編集してください。
<x-layout>
<x-slot:title>
404 Not Found
</x-slot>
<h1 class="mt-5">404 Not Found</h1>
<a href="{{ route('member.index') }}">メンバーリストへ戻る</a>
</x-layout>
動作確認
http://localhost:8888/member/100にアクセスしてください。idの「100」は存在しないidであれば何でもかまいません。下記のように表示されれば成功です。
解説は以上です。次回はこのCRUDシステムのテストコードを実装します。おつかれさまでした。
PHP/Laravelのシステム開発は株式会社パパグラムへぜひご相談ください。