【第四弾】Laravel入門資料
- Windows環境構築
- Mac環境構築
- Laravelとは
- ルーティング
- ビュー
- コントローラ
- マイグレーション
- モデル
- ここまでの知識の実践
- 検索・フィルタ
- ソフトデリート
- ページネーション
- 画像アップロード
- 認証ライブラリ
- 認証実装
- ミドルウェア・ロール
- 総合実践
📚 Laravel公式ドキュメント
この総合実践では、以下の公式ドキュメントで学んだ知識を全て統合します:
💡 これまでの学習を振り返りながら、実践的なアプリケーションを構築しましょう。
総合実践: TODOアプリを作ろう
これまで学んだ全ての知識を使って、実践的なTODOアプリを作成します。
認証、CRUD、ロール管理、ページネーション、検索など、全てを統合したアプリケーションです。
🎯 このアプリで使う技術
- ✅ 認証: ユーザー登録・ログイン・ログアウト
- ✅ CRUD: タスクの作成・表示・更新・削除
- ✅ ロール管理: 管理者と一般ユーザーの権限分け
- ✅ リレーション: ユーザーとタスクの1対多
- ✅ ページネーション: タスク一覧のページ分け
- ✅ 検索・フィルタリング: ステータス別、キーワード検索
- ✅ バリデーション: 入力値チェック
- ✅ ミドルウェア: 認証・権限チェック
📋 アプリの機能
- • 一般ユーザー: 自分のタスクの作成・編集・削除・完了
- • 管理者: 全ユーザーのタスクを閲覧・削除可能
- • ステータス管理: 未着手・進行中・完了の3状態
- • 優先度: 低・中・高の3段階
- • 期限設定: タスクの期限を設定
ステップ1: データベース設計
必要なテーブル
users テーブル
├─ id
├─ name
├─ email (unique)
├─ password
├─ role (default: 'user') ← 'user' or 'admin'
├─ created_at
└─ updated_at
tasks テーブル
├─ id
├─ user_id (外部キー → users.id)
├─ title
├─ description (nullable)
├─ status (default: 'pending') ← 'pending', 'in_progress', 'completed'
├─ priority (default: 'medium') ← 'low', 'medium', 'high'
├─ due_date (nullable)
├─ completed_at (nullable)
├─ created_at
└─ updated_at
マイグレーション作成
# Laravelプロジェクト作成(まだの場合)
composer create-project laravel/laravel todo-app
cd todo-app
# usersテーブルにroleカラム追加
php artisan make:migration add_role_to_users_table
# tasksテーブル作成
php artisan make:migration create_tasks_table
// database/migrations/xxxx_add_role_to_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('role')->default('user')->after('password');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};
// database/migrations/xxxx_create_tasks_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('description')->nullable();
$table->enum('status', ['pending', 'in_progress', 'completed'])->default('pending');
$table->enum('priority', ['low', 'medium', 'high'])->default('medium');
$table->date('due_date')->nullable();
$table->timestamp('completed_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
# マイグレーション実行
php artisan migrate
✅ データベース設計が完了しました!
ステップ2: モデル作成
Userモデルの修正
// app/Models/User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'password',
'role',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
/**
* ユーザーが持つタスク
*/
public function tasks()
{
return $this->hasMany(Task::class);
}
/**
* 管理者かどうか
*/
public function isAdmin(): bool
{
return $this->role === 'admin';
}
/**
* 特定のロールを持っているか
*/
public function hasRole(string $role): bool
{
return $this->role === $role;
}
}
Taskモデルの作成
# Taskモデル作成
php artisan make:model Task
// app/Models/Task.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = [
'user_id',
'title',
'description',
'status',
'priority',
'due_date',
'completed_at',
];
protected $casts = [
'due_date' => 'date',
'completed_at' => 'datetime',
];
/**
* タスクの所有者
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* 完了済みかどうか
*/
public function isCompleted(): bool
{
return $this->status === 'completed';
}
/**
* 期限切れかどうか
*/
public function isOverdue(): bool
{
if (!$this->due_date || $this->isCompleted()) {
return false;
}
return $this->due_date->isPast();
}
/**
* タスクを完了にする
*/
public function markAsCompleted(): void
{
$this->update([
'status' => 'completed',
'completed_at' => now(),
]);
}
/**
* 優先度の色を取得(Tailwind CSS用)
*/
public function getPriorityColor(): string
{
return match($this->priority) {
'low' => 'green',
'medium' => 'yellow',
'high' => 'red',
default => 'gray',
};
}
/**
* ステータスの色を取得
*/
public function getStatusColor(): string
{
return match($this->status) {
'pending' => 'gray',
'in_progress' => 'blue',
'completed' => 'green',
default => 'gray',
};
}
}
✅ モデルの作成が完了しました!
ステップ3: 認証機能の実装
認証コントローラーの作成
# コントローラー作成
php artisan make:controller Auth/RegisterController
php artisan make:controller Auth/LoginController
php artisan make:controller Auth/LogoutController
// app/Http/Controllers/Auth/RegisterController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RegisterController extends Controller
{
public function showRegistrationForm()
{
return view('auth.register');
}
public function register(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => $validated['password'],
]);
Auth::login($user);
return redirect()->route('tasks.index')
->with('success', 'アカウントを作成しました!');
}
}
// app/Http/Controllers/Auth/LoginController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function showLoginForm()
{
return view('auth.login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended(route('tasks.index'))
->with('success', 'ログインしました!');
}
return back()->withErrors([
'email' => 'メールアドレスまたはパスワードが正しくありません。',
])->onlyInput('email');
}
}
// app/Http/Controllers/Auth/LogoutController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LogoutController extends Controller
{
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login')
->with('success', 'ログアウトしました。');
}
}
認証ルートの設定
// routes/web.php
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\LogoutController;
// ゲスト専用ルート
Route::middleware('guest')->group(function () {
Route::get('/register', [RegisterController::class, 'showRegistrationForm'])->name('register');
Route::post('/register', [RegisterController::class, 'register']);
Route::get('/login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('/login', [LoginController::class, 'login']);
});
// 認証済みユーザー専用
Route::middleware('auth')->group(function () {
Route::post('/logout', [LogoutController::class, 'logout'])->name('logout');
});
✅ 認証機能の実装が完了しました!
ステップ4: タスクのCRUD実装
TaskControllerの作成
# リソースコントローラー作成
php artisan make:controller TaskController --resource
// app/Http/Controllers/TaskController.php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
/**
* タスク一覧表示(ページネーション・検索付き)
*/
public function index(Request $request)
{
$query = Task::query();
// ログインユーザーのタスクのみ(管理者は全タスク表示)
if (!auth()->user()->isAdmin()) {
$query->where('user_id', auth()->id());
} else {
// 管理者の場合はユーザー情報も一緒に取得
$query->with('user');
}
// ステータスフィルタ
if ($request->filled('status')) {
$query->where('status', $request->status);
}
// 優先度フィルタ
if ($request->filled('priority')) {
$query->where('priority', $request->priority);
}
// キーワード検索
if ($request->filled('search')) {
$query->where(function($q) use ($request) {
$q->where('title', 'like', '%' . $request->search . '%')
->orWhere('description', 'like', '%' . $request->search . '%');
});
}
// 並び順(期限が近い順)
$query->orderByRaw("CASE WHEN status = 'completed' THEN 1 ELSE 0 END")
->orderBy('due_date', 'asc')
->orderBy('created_at', 'desc');
$tasks = $query->paginate(10)->withQueryString();
return view('tasks.index', compact('tasks'));
}
/**
* タスク作成フォーム表示
*/
public function create()
{
return view('tasks.create');
}
/**
* タスク保存
*/
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'priority' => 'required|in:low,medium,high',
'due_date' => 'nullable|date|after_or_equal:today',
]);
auth()->user()->tasks()->create($validated);
return redirect()->route('tasks.index')
->with('success', 'タスクを作成しました!');
}
/**
* タスク詳細表示
*/
public function show(Task $task)
{
// 権限チェック
$this->authorize('view', $task);
return view('tasks.show', compact('task'));
}
/**
* タスク編集フォーム表示
*/
public function edit(Task $task)
{
// 権限チェック
$this->authorize('update', $task);
return view('tasks.edit', compact('task'));
}
/**
* タスク更新
*/
public function update(Request $request, Task $task)
{
// 権限チェック
$this->authorize('update', $task);
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'status' => 'required|in:pending,in_progress,completed',
'priority' => 'required|in:low,medium,high',
'due_date' => 'nullable|date',
]);
// ステータスが完了に変わった場合、completed_atを設定
if ($validated['status'] === 'completed' && $task->status !== 'completed') {
$validated['completed_at'] = now();
} elseif ($validated['status'] !== 'completed') {
$validated['completed_at'] = null;
}
$task->update($validated);
return redirect()->route('tasks.index')
->with('success', 'タスクを更新しました!');
}
/**
* タスク削除
*/
public function destroy(Task $task)
{
// 権限チェック
$this->authorize('delete', $task);
$task->delete();
return redirect()->route('tasks.index')
->with('success', 'タスクを削除しました。');
}
/**
* タスクを完了にする
*/
public function complete(Task $task)
{
// 権限チェック
$this->authorize('update', $task);
$task->markAsCompleted();
return back()->with('success', 'タスクを完了しました!');
}
}
ルート設定
// routes/web.php
use App\Http\Controllers\TaskController;
Route::middleware('auth')->group(function () {
// タスク一覧をトップページに
Route::get('/', [TaskController::class, 'index'])->name('tasks.index');
// タスクのリソースルート
Route::resource('tasks', TaskController::class);
// タスク完了
Route::post('/tasks/{task}/complete', [TaskController::class, 'complete'])
->name('tasks.complete');
});
✅ タスクのCRUD実装が完了しました!
ステップ5: ポリシーで権限管理
TaskPolicyの作成
# ポリシー作成
php artisan make:policy TaskPolicy --model=Task
// app/Policies/TaskPolicy.php
namespace App\Policies;
use App\Models\Task;
use App\Models\User;
class TaskPolicy
{
/**
* タスクを表示できるか
*/
public function view(User $user, Task $task): bool
{
// 管理者または自分のタスク
return $user->isAdmin() || $task->user_id === $user->id;
}
/**
* タスクを作成できるか
*/
public function create(User $user): bool
{
// 認証済みユーザーなら誰でも作成可能
return true;
}
/**
* タスクを更新できるか
*/
public function update(User $user, Task $task): bool
{
// 自分のタスクのみ更新可能(管理者は更新不可)
return $task->user_id === $user->id;
}
/**
* タスクを削除できるか
*/
public function delete(User $user, Task $task): bool
{
// 管理者または自分のタスク
return $user->isAdmin() || $task->user_id === $user->id;
}
}
ポリシーの登録
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use App\Models\Task;
use App\Policies\TaskPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// ポリシーの登録(Laravel 11では自動検出されるが明示的に登録も可能)
Gate::policy(Task::class, TaskPolicy::class);
}
}
Policyの使い方:
// コントローラーで
$this->authorize('update', $task);
// Bladeで
@can('update', $task)
<a href="{{ route('tasks.edit', $task) }}">編集</a>
@endcan
// コードで
if (auth()->user()->can('delete', $task)) {
// 削除処理
}
✅ ポリシーでの権限管理が完了しました!
ステップ6: 管理者用ミドルウェア
IsAdminミドルウェアの作成
php artisan make:middleware IsAdmin
// app/Http/Middleware/IsAdmin.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class IsAdmin
{
public function handle(Request $request, Closure $next)
{
if (!auth()->check()) {
return redirect()->route('login');
}
if (!auth()->user()->isAdmin()) {
abort(403, 'このページにアクセスする権限がありません。');
}
return $next($request);
}
}
ミドルウェアの登録
// bootstrap/app.php
use App\Http\Middleware\IsAdmin;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'admin' => IsAdmin::class,
]);
})
->create();
管理者専用ルート(オプション)
// routes/web.php
use App\Http\Controllers\Admin\DashboardController;
// 管理者専用ルート
Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/users', [DashboardController::class, 'users'])->name('users');
});
✅ 管理者用ミドルウェアが完成しました!
ステップ7: シーダーで初期データ作成
シーダーの作成
# シーダー作成
php artisan make:seeder UserSeeder
php artisan make:seeder TaskSeeder
// database/seeders/UserSeeder.php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
public function run(): void
{
// 管理者ユーザー
User::create([
'name' => '管理者',
'email' => 'admin@example.com',
'password' => 'password',
'role' => 'admin',
]);
// 一般ユーザー
User::create([
'name' => '田中太郎',
'email' => 'tanaka@example.com',
'password' => 'password',
'role' => 'user',
]);
User::create([
'name' => '佐藤花子',
'email' => 'sato@example.com',
'password' => 'password',
'role' => 'user',
]);
}
}
// database/seeders/TaskSeeder.php
namespace Database\Seeders;
use App\Models\Task;
use App\Models\User;
use Illuminate\Database\Seeder;
class TaskSeeder extends Seeder
{
public function run(): void
{
$tanaka = User::where('email', 'tanaka@example.com')->first();
$sato = User::where('email', 'sato@example.com')->first();
// 田中太郎のタスク
Task::create([
'user_id' => $tanaka->id,
'title' => 'Laravelの勉強',
'description' => 'ルーティング、コントローラー、モデルを学ぶ',
'status' => 'in_progress',
'priority' => 'high',
'due_date' => now()->addDays(3),
]);
Task::create([
'user_id' => $tanaka->id,
'title' => 'TODOアプリの作成',
'description' => '認証機能とCRUD機能を実装',
'status' => 'pending',
'priority' => 'medium',
'due_date' => now()->addWeek(),
]);
Task::create([
'user_id' => $tanaka->id,
'title' => 'データベース設計書を作成',
'description' => 'ER図とテーブル定義書',
'status' => 'completed',
'priority' => 'low',
'completed_at' => now()->subDays(2),
]);
// 佐藤花子のタスク
Task::create([
'user_id' => $sato->id,
'title' => 'プレゼン資料作成',
'description' => '来週のミーティング用',
'status' => 'pending',
'priority' => 'high',
'due_date' => now()->addDays(5),
]);
Task::create([
'user_id' => $sato->id,
'title' => 'コードレビュー',
'description' => 'プルリクエスト#123をレビュー',
'status' => 'in_progress',
'priority' => 'medium',
'due_date' => now()->addDay(),
]);
}
}
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
UserSeeder::class,
TaskSeeder::class,
]);
}
}
# シーダー実行
php artisan db:seed
# または、マイグレーション+シーダーを一度に実行
php artisan migrate:fresh --seed
✅ 初期データの作成が完了しました!
ステップ8: Tinkerで動作確認
Tinkerで実践してみよう
# Tinker起動
php artisan tinker
1. ユーザーとタスクの確認
// 全ユーザーを取得
User::all();
// 田中太郎のタスクを取得
$tanaka = User::where('email', 'tanaka@example.com')->first();
$tanaka->tasks;
// タスク数をカウント
$tanaka->tasks()->count();
// => 3
// 未完了のタスクのみ
$tanaka->tasks()->where('status', '!=', 'completed')->get();
2. タスクの作成
// 新しいタスクを作成
$tanaka->tasks()->create([
'title' => 'Tinkerから作成したタスク',
'description' => 'Tinkerでタスク作成をテスト',
'priority' => 'high',
'due_date' => now()->addDays(7),
]);
// 確認
$tanaka->tasks()->latest()->first();
3. タスクの更新
// タスクを取得
$task = Task::find(1);
// ステータスを更新
$task->update(['status' => 'in_progress']);
// タスクを完了にする
$task->markAsCompleted();
// 確認
$task->fresh();
$task->status;
// => "completed"
$task->completed_at;
// => Carbon\Carbon instance
4. リレーションの確認
// タスクから所有者を取得
$task = Task::find(1);
$task->user;
// => User {name: "田中太郎", ...}
$task->user->name;
// => "田中太郎"
// Eager Loading(N+1問題を回避)
$tasks = Task::with('user')->get();
foreach ($tasks as $task) {
echo $task->title . ' - ' . $task->user->name . "\n";
}
5. 権限チェックのテスト
// 管理者ユーザー
$admin = User::where('email', 'admin@example.com')->first();
$admin->isAdmin();
// => true
// 一般ユーザー
$tanaka = User::where('email', 'tanaka@example.com')->first();
$tanaka->isAdmin();
// => false
// ログインシミュレーション
Auth::login($tanaka);
auth()->user()->name;
// => "田中太郎"
auth()->user()->isAdmin();
// => false
6. 検索・フィルタリング
// ステータスでフィルタ
Task::where('status', 'completed')->get();
// 優先度が高いタスク
Task::where('priority', 'high')->get();
// 期限が近いタスク(7日以内)
Task::where('due_date', '<=', now()->addDays(7))
->where('status', '!=', 'completed')
->orderBy('due_date', 'asc')
->get();
// キーワード検索
Task::where('title', 'like', '%Laravel%')->get();
// 複合検索
Task::where('user_id', $tanaka->id)
->where('status', 'pending')
->where('priority', 'high')
->get();
7. ページネーション
// ページネーション(1ページ10件)
$tasks = Task::paginate(10);
$tasks->total(); // 総件数
$tasks->perPage(); // 1ページあたりの件数
$tasks->currentPage(); // 現在のページ番号
$tasks->lastPage(); // 最終ページ番号
// 2ページ目を取得
$tasks = Task::paginate(10, ['*'], 'page', 2);
8. 統計情報の取得
// 田中太郎のタスク統計
$tanaka = User::where('email', 'tanaka@example.com')->first();
// ステータス別カウント
$pendingCount = $tanaka->tasks()->where('status', 'pending')->count();
$inProgressCount = $tanaka->tasks()->where('status', 'in_progress')->count();
$completedCount = $tanaka->tasks()->where('status', 'completed')->count();
echo "未着手: {$pendingCount}\n";
echo "進行中: {$inProgressCount}\n";
echo "完了: {$completedCount}\n";
// 期限切れタスク数
$overdueCount = $tanaka->tasks()
->where('due_date', '<', now())
->where('status', '!=', 'completed')
->count();
echo "期限切れ: {$overdueCount}\n";
✅ Tinkerでの動作確認が完了しました!
まとめ
TODOアプリで使った技術
1. データベース設計
- マイグレーションでusers、tasksテーブル作成
- 外部キー制約でリレーション定義
- enum型でステータス・優先度管理
2. モデルとリレーション
- User hasMany Task(1対多)
- Task belongsTo User
- カスタムメソッド(isCompleted、isOverdue、markAsCompleted)
- Eager Loadingでn+1問題回避
3. 認証機能
- ユーザー登録・ログイン・ログアウト
- パスワードのハッシュ化
- セッション管理
- guestミドルウェアでログイン済みはリダイレクト
4. CRUD操作
- リソースコントローラーで7つのアクション
- index(一覧)、create(作成フォーム)、store(保存)
- show(詳細)、edit(編集フォーム)、update(更新)、destroy(削除)
- バリデーションで入力チェック
5. 権限管理
- ロール(admin、user)でアクセス制御
- Policyで細かい権限チェック
- 自分のタスクのみ編集可能
- 管理者は全タスク閲覧・削除可能
6. 検索・フィルタリング
- ステータス・優先度でフィルタ
- キーワード検索(タイトル・説明文)
- 期限順・作成日順でソート
- 完了タスクは下に表示
7. ページネーション
- paginate(10)で1ページ10件
- withQueryString()で検索条件を保持
- ページリンクの自動生成
8. シーダーとTinker
- シーダーで初期データ作成
- Tinkerでデータ操作・確認
- リレーション・権限チェックのテスト
学んだLaravelの機能一覧
基礎
- ルーティング
- コントローラー
- ビュー(Blade)
- マイグレーション
- モデル
データベース
- Eloquent ORM
- リレーション
- クエリビルダー
- シーダー
- Tinker
認証・権限
- ユーザー認証
- ミドルウェア
- ロール管理
- Policy
- Gate
応用
- ページネーション
- 検索・フィルタリング
- バリデーション
- フラッシュメッセージ
- リダイレクト
次のステップ
TODOアプリの基本が完成しました!さらに機能を追加してみましょう:
- カテゴリ機能: タスクをカテゴリ分け(仕事・プライベートなど)
- タグ機能: 多対多リレーションでタグ管理
- コメント機能: タスクにコメントを追加
- ファイル添付: 画像アップロード機能の応用
- 通知機能: 期限が近いタスクをメール通知
- API化: RESTful APIでSPA対応
- テスト: PHPUnitでテストコード作成
🎉 総合実践TODOアプリが完成しました!
これまで学んだ全ての知識を使って、実践的なアプリケーションを作成できました。
認証、CRUD、権限管理、リレーション、ページネーション、検索など、実務で必要な機能を全て実装しました。
この知識を活かして、さらに複雑なアプリケーションにチャレンジしてみましょう!