11.

Laravel MVC 基礎:Controller でモデルに値を保存し、View へ渡す方法(compact / with / 直接配列)

編集
この記事の要点
  • Controller でモデル保存: Model::create($request->validated()) または $model = new Model; $model->fill(...); $model->save();
  • View への受け渡し 3 通り: compact() / 連想配列 / ->with()
  • Blade では {{ $post->title }} でアクセス(自動 HTML エスケープ付き)
  • Mass Assignment 対策: モデル側に $fillable または $guarded を必ず設定
  • 保存前に $request->validate([...]) でバリデーションを通すのが Laravel 標準

Laravel MVC の基本構造

MVC とは Model(DB との対話)・View(HTML 表示)・Controller(リクエスト処理)の三層に分ける設計パターンです。Laravel での典型的な流れ:

1) ブラウザ
   ↓ POST /posts
2) Route (routes/web.php)
   ↓ PostController@store
3) Controller (app/Http/Controllers/PostController.php)
   ・$request->validate(...)
   ・Post::create(...)             ← Model に保存
   ↓
4) View (resources/views/posts/show.blade.php)
   ・{{ $post->title }}            ← 表示
   ↑
5) ブラウザに HTML が返る

1. ルート定義

// routes/web.php
use App\Http\Controllers\PostController;

Route::get('/posts',         [PostController::class, 'index']);
Route::get('/posts/create',  [PostController::class, 'create']);
Route::post('/posts',        [PostController::class, 'store']);
Route::get('/posts/{post}',  [PostController::class, 'show']);

// 一括(リソースコントローラ)
Route::resource('posts', PostController::class);

2. モデル定義(Mass Assignment 対策込み)

# モデル + マイグレーション + コントローラ + リクエストを一括生成
php artisan make:model Post -mcr --requests
// app/Models/Post.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Post extends Model {
    use HasFactory;

    // ✅ 入力許可するカラムを明示(推奨)
    protected $fillable = ['title', 'body', 'user_id', 'published_at'];

    // または逆指定(基本これは使わない)
    // protected $guarded = ['id'];

    // 型キャスト
    protected $casts = [
        'published_at' => 'datetime',
        'meta'         => 'array',
    ];
}

$fillable または $guarded を設定しないと Mass Assignment(一括代入)で 例外が出ます。後述の Post::create($request->all()) も動きません。

3. Controller: モデルに値を保存する

// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;

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

class PostController extends Controller {

    // 一覧
    public function index() {
        $posts = Post::orderBy('id', 'desc')->paginate(20);
        return view('posts.index', compact('posts'));
    }

    // 新規作成フォーム表示
    public function create() {
        return view('posts.create');
    }

    // 保存 ← 本記事のメイン
    public function store(Request $request) {
        // 1) バリデーション
        $validated = $request->validate([
            'title' => 'required|string|max:100',
            'body'  => 'required|string',
        ]);

        // 2) 認証ユーザを紐付け
        $validated['user_id'] = auth()->id();

        // 3) モデルに保存(4 通りのいずれか)
        // 方法 A: create で一発(fillable 必須)
        $post = Post::create($validated);

        // 方法 B: new + save
        // $post = new Post();
        // $post->fill($validated);
        // $post->save();

        // 方法 C: プロパティに直接代入
        // $post = new Post();
        // $post->title = $validated['title'];
        // $post->body  = $validated['body'];
        // $post->save();

        // 方法 D: リレーション経由
        // $post = auth()->user()->posts()->create($validated);

        // 4) View へリダイレクト or レンダリング
        return redirect()->route('posts.show', $post)
            ->with('success', '投稿しました');
    }

    // 詳細表示
    public function show(Post $post) {
        return view('posts.show', compact('post'));
    }
}

4. View に値を渡す 3 つの方法

// 方法 1: compact() —— もっとも一般的
$post = Post::find(1);
$comments = $post->comments;
return view('posts.show', compact('post', 'comments'));

// 方法 2: 連想配列で直接
return view('posts.show', [
    'post'     => Post::find(1),
    'comments' => Comment::where('post_id', 1)->get(),
]);

// 方法 3: with() チェーン
return view('posts.show')
    ->with('post', Post::find(1))
    ->with('comments', Comment::where('post_id', 1)->get());

// 方法 4: withXxx() マジックメソッド
return view('posts.show')
    ->withPost(Post::find(1))            // → $post
    ->withComments(Comment::all());      // → $comments

5. Blade テンプレートでの表示

{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')

@section('content')
    

{{ $post->title }}

{{-- 自動 HTML エスケープ --}}

{!! nl2br(e($post->body)) !!}

{{-- e() でエスケープしてから改行変換 --}} 投稿者: {{ $post->user->name }}

コメント ({{ $comments->count() }} 件)

@forelse ($comments as $c)
{{ $c->user->name }}

{{ $c->body }}

@empty

まだコメントはありません

@endforelse @if (session('success'))
{{ session('success') }}
@endif @endsection

6. Form Request でバリデーションを分離(推奨)

Controller が肥大化するのを防ぐため、バリデーションは Form Request クラスに切り出すのが定石です:

php artisan make:request StorePostRequest
// app/Http/Requests/StorePostRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest {
    public function authorize(): bool {
        return auth()->check();
    }

    public function rules(): array {
        return [
            'title' => 'required|string|max:100|unique:posts,title',
            'body'  => 'required|string|min:10',
        ];
    }

    public function messages(): array {
        return [
            'title.required' => 'タイトルを入力してください',
            'title.unique'   => 'そのタイトルは既に使われています',
        ];
    }
}

// Controller
public function store(StorePostRequest $request) {
    $post = Post::create($request->validated() + [
        'user_id' => auth()->id(),
    ]);
    return redirect()->route('posts.show', $post);
}

7. 更新(update)

public function update(UpdatePostRequest $request, Post $post) {
    $post->update($request->validated());

    // 個別代入版
    // $post->title = $request->input('title');
    // $post->body  = $request->input('body');
    // $post->save();

    return redirect()->route('posts.show', $post)
        ->with('success', '更新しました');
}

Mass Assignment の罠

// ❌ $fillable 未設定の場合
$post = Post::create(['title' => 'hi', 'body' => 'foo']);
// → Illuminate\Database\Eloquent\MassAssignmentException

// ❌ 危険な書き方
// フォームに  を仕込まれると
Post::create($request->all());
// → is_admin まで保存されてしまう

// ✅ 安全な書き方
Post::create($request->only(['title', 'body']));
Post::create($request->validated());           // Form Request

$fillable と $guarded の違い

属性意味運用
$fillable = [...]許可するカラムを明示(ホワイトリスト)推奨
$guarded = [...]禁止するカラムを明示(ブラックリスト)非推奨(漏れがち)
$guarded = []すべて許可絶対避ける

FAQ

Q: Post::create() で「Add [field] to fillable」エラー
A: 該当カラムを $fillable 配列に追加してください。id, created_at, updated_at は自動なので不要。

Q: $post->title$post->getAttribute('title') の違い
A: 同じです。getAttribute は内部で呼ばれるメソッドで、Accessor を経由します。

Q: View で {{ $post }} と書くとどうなる
A: モデルが JSON シリアライズされて表示されます。$casts$hidden(除外したい属性)も反映されます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストール(eclipseプラグイン)
  2. クイックスタート
  3. プロジェクトの作成
  4. Spring Bootプロジェクトの作成
  5. Spring Bootプロジェクトの実行
  6. Spring BootでHello World!
  7. アノテーション一覧
  8. DB接続設定からエンティティおよびリポジトリの作成、値の取得まで(JPA編)
  9. DB接続設定や値の取得(JdbcTemplate編)
  10. ビューから値をモデルに格納しコントローラーで受け取る方法
  11. コントローラーにてモデルに値を格納してビューに渡す方法
  12. テンプレートエンジン
  13. ModelとModelAndViewの違い
  14. AOPの使用方法
  15. classpath: 内部ファイルの読み込み
  16. file: 外部ファイルの読み込み
  17. CSVファイルアップロード方法(Ajax)
  18. CSVファイルダウンロード方法(Ajax)
  19. Spring Bootプロジェクトのビルドと本番環境へのデプロイ方法(内部tomcat使用)
  20. Application.propertiesの環境依存設定の分割方法
  21. JPAにおけるEntityManagerの取得方法
  22. JPAにおけるjava.sql.Connectionの取得方法
  23. エラー一覧
  24. jarの引数を受け取る方法
  25. Spring BootでGmailからメール送信
  26. 複数のDBに接続する設定(Spring Boot & JPA編)
  27. ポート番号の変更
  28. Basic認証の実装と特定のURLに限定する方法
  29. Spring SecurityのBasic認証の無効化
  30. 独自のエラーページを定義する方法
  31. プロパティファイルの値やjar実行時の引数を取得する方法