6.

Laravel コントローラー入門 — 生成 / resource / invokable / DI / バリデーション

編集
この記事の要点
  • 生成: php artisan make:controller PostController--resource で CRUD 7 メソッド込み
  • シングルアクション: --invokable__invoke() 1 メソッドだけのコントローラ
  • DI: メソッド引数に Request $request / Post $post を書くだけで自動注入
  • Route Model Binding: /posts/{post} パラメータが自動で Eloquent モデルに
  • バリデーション: $request->validate([...]) または FormRequest クラスに切り出し
  • ミドルウェアはコンストラクタ$this->middleware('auth')->except('index')

コントローラーの生成

# 基本
php artisan make:controller PostController

# CRUD 7 メソッド入りのリソースコントローラ
php artisan make:controller PostController --resource

# API リソース(create/edit 無し、5 メソッド)
php artisan make:controller Api/PostController --api

# モデルと連携(型ヒント自動)
php artisan make:controller PostController --resource --model=Post

# シングルアクション(__invoke のみ)
php artisan make:controller SendWelcomeMail --invokable

基本的なコントローラ

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    // 一覧
    public function index()
    {
        $posts = Post::latest()->paginate(20);
        return view('posts.index', compact('posts'));
    }

    // 詳細
    public function show(Post $post)             // ← Route Model Binding
    {
        return view('posts.show', compact('post'));
    }

    // フォーム
    public function create()
    {
        return view('posts.create');
    }

    // 保存
    public function store(Request $request)
    {
        $data = $request->validate([
            'title' => 'required|string|max:255',
            'body'  => 'required|string',
        ]);
        $post = Post::create($data + ['user_id' => $request->user()->id]);
        return redirect()->route('posts.show', $post)
            ->with('status', '保存しました');
    }

    // 更新
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);
        $post->update($request->validate([
            'title' => 'required|string|max:255',
            'body'  => 'required|string',
        ]));
        return redirect()->route('posts.show', $post);
    }

    // 削除
    public function destroy(Post $post)
    {
        $this->authorize('delete', $post);
        $post->delete();
        return redirect()->route('posts.index');
    }
}

リソースルートとコントローラの対応

HTTPURIメソッド名前
GET/postsindexposts.index
GET/posts/createcreateposts.create
POST/postsstoreposts.store
GET/posts/{post}showposts.show
GET/posts/{post}/editeditposts.edit
PUT/PATCH/posts/{post}updateposts.update
DELETE/posts/{post}destroyposts.destroy

シングルアクションコントローラ

// php artisan make:controller SendWelcomeMail --invokable

class SendWelcomeMail extends Controller
{
    public function __invoke(Request $request, User $user)
    {
        Mail::to($user)->send(new Welcome($user));
        return back()->with('status', 'sent');
    }
}

// ルート定義
Route::post('/users/{user}/welcome', SendWelcomeMail::class);

依存性注入(DI)

// メソッド DI(HTTP リクエストごとに解決)
public function store(Request $request, PostService $service)
{
    $post = $service->create($request->validated());
    return redirect()->route('posts.show', $post);
}

// コンストラクタ DI
class PostController extends Controller
{
    public function __construct(private PostService $service) {}

    public function index()
    {
        return $this->service->latest();
    }
}

バリデーション(3 通り)

// 方法 A: コントローラ内 (簡単)
public function store(Request $request)
{
    $data = $request->validate([
        'title' => 'required|string|max:255',
        'tags'  => 'array',
        'tags.*'=> 'string|max:50',
    ]);
}

// 方法 B: FormRequest クラスに切り出し (推奨)
// php artisan make:request StorePostRequest
class StorePostRequest extends FormRequest
{
    public function authorize(): bool { return true; }
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'body'  => 'required|string',
        ];
    }
    public function messages(): array
    {
        return ['title.required' => 'タイトルは必須です'];
    }
}
public function store(StorePostRequest $request) {
    $post = Post::create($request->validated());
}

// 方法 C: Validator ファサード(細かい制御)
$validator = Validator::make($request->all(), [...]);
if ($validator->fails()) {
    return back()->withErrors($validator)->withInput();
}

ミドルウェアの適用

class PostController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('verified')->only(['store', 'update', 'destroy']);
        $this->middleware('can:update,post')->except(['index', 'show']);
    }
}

// あるいはルート定義側で
Route::resource('posts', PostController::class)
    ->middleware(['auth', 'verified']);

レスポンスのバリエーション

// ビュー
return view('posts.show', compact('post'));

// JSON
return response()->json(['ok' => true, 'data' => $post]);

// API Resource
return new PostResource($post);
return PostResource::collection($posts);

// ステータスコード
return response()->json([...], 201);
abort(404, 'Post not found');
abort_if(! $user->is_admin, 403);

// リダイレクト
return redirect('/dashboard');
return redirect()->route('posts.show', $post);
return back()->withInput()->withErrors(['title' => 'NG']);

// ファイルダウンロード
return response()->download(storage_path('app/report.pdf'));
return response()->streamDownload(fn () => print($csv), 'report.csv');

ページネーション

public function index()
{
    $posts = Post::with('user')->latest()->paginate(20);
    return view('posts.index', compact('posts'));
}
@foreach ($posts as $post)
  

{{ $post->title }}

@endforeach {{ $posts->links() }} {{-- ページネーション UI --}} {{ $posts->total() }} {{-- 合計件数 --}} {{ $posts->currentPage() }}

FAQ

Q: コントローラを名前空間で分けたい
A: App\Http\Controllers\Admin\UserController 等。make:controller Admin/UserController でフォルダ付きで生成。

Q: 単体テストでコントローラをテストしたい
A: HTTP テスト機能で $this->get('/posts')->assertStatus(200)。コントローラ単体ではなくルート経由でテストするのが Laravel 流。

Q: API と Web で同じコントローラを使い回したい
A: アクション内で $request->wantsJson()$request->is('api/*') で分岐、または別コントローラに分けるのが推奨。

編集
Post Share
子ページ
  1. コントローラー内で他のコントローラーを呼び出す方法
同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)