1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PHP学習4日目】MVCの復習とフォームの送信、DB保存を学んだ

Last updated at Posted at 2025-08-16

はじめに

プログラミング学習4日目として、LaravelのCRUD機能(Create, Read, Update, Delete)実装の第一歩、「C(Create)」と「R(Read)」に挑戦しました。
前回(3日目)学んだMVCパターンの知識を活かし、より実践的な投稿機能を作成する中で発生した問題と、その解決プロセスを詳しく解説します。

開発環境

カテゴリ バージョン/ツール
OS macOS
PHP 8.4.1(MAMP使用)
Laravel 11.x
データベース SQLite

4日目の学習計画

Part 1: 復習

  • MVCパターンの再確認
  • 既存機能(投稿一覧表示)の動作確認
  • 個別投稿ページの表示機能実装

Part 2: 新機能実装

  • 投稿作成フォームの実装
  • フォームデータの保存処理
  • ルーティングの最適化

Part1: 復習と個別投稿ページの実装

まずは3日目までの機能が正しく動作するかを確認しつつ、個別投稿ページ(/posts/{id})を完成させます。

ファイル構成の確認

現在の主要なファイル構成です。今回、個別投稿ページ用のpost.blade.phpを追加しました。

app/
├── Http/Controllers/HelloController.php  #  コントローラー
├── Models/Post.php                       #  モデル
database/
├── migrations/
│   └── 2025_08_14_061955_create_posts_table.php #  マイグレーション
resources/views/
├── posts.blade.php                       #  投稿一覧画面
├── post.blade.php                        #  個別投稿画面(今回追加)
└── create-post.blade.php                 #  投稿作成画面(今回追加)
routes/
└── web.php                               #  ルーティング

MVCの役割(復習)

1000022740.png

Model (Post.php)

Eloquent ORMを使い、データベースのpostsテーブルと連携します。

app/models/post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // Mass Assignment(一括代入)を許可するカラムを指定
    protected $fillable = ['title', 'content'];
}

ポイント

  • $fillable: Post::create()などで一括して値を設定できるカラムを定義し、意図しないカラムへの代入を防ぎます。

Controller (HelloController.php)

Postモデルを使ってデータを取得し、ビューに渡します。

app/http/controllers/HelloController.php
<?php
// ... (他のuse文)
use App\Models\Post;

class HelloController extends Controller
{
    // 投稿一覧を取得
    public function posts()
    {
        $posts = Post::all(); // 全ての投稿を取得
        return view('posts', ['posts' => $posts]); // ビューにデータを渡す
    }

    // 個別の投稿を取得
    public function showPost($id)
    {
        $post = Post::findOrFail($id); // IDで投稿を検索(見つからない場合は404エラー)
        return view('post', compact('post')); // compact関数でデータを渡す
    }
}

ポイント

  • Post::all(): 全てのレコードを取得します。
  • Post::findOrFail($id): 指定したIDのレコードを取得します。レコードが存在しない場合、自動的に404ページを表示してくれる便利なメソッドです。
  • compact('post'): ['post' => $post] と同じ意味で、変数をビューに渡す際の短縮記法です。

問題1: 個別投稿ページが表示されない!

エラー: View [post] not found
URL: http://127.0.0.1:8000/posts/1

原因:
コントローラーはview('post', ...)post.blade.phpを呼び出そうとしていましたが、単純なファイル名のタイポ(post2.blade.phpと作成していた)が原因でした。

解決策:
resources/viewsディレクトリに、正しい名前でpost.blade.phpを作成しました。

resources/views/post.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ $post->title }}</title>
</head>
<body>
    <h1>{{ $post->title }}</h1>
    <p>{!! nl2br(e($post->content)) !!}</p>
    <small>作成日時: {{ $post->created_at }}</small>
    
    <br><br>
    <a href="{{ route('posts.index') }}">投稿一覧へ戻る</a>
</body>
</html>

ワンポイント

  • {!! nl2br(e($post->content)) !!}: e()でXSS対策としてHTMLエスケープを行った後、nl2br()で改行文字(\n)を<br>タグに変換しています。{!! !!}はHTMLをエスケープせずに出力するための記法です。

Part2: 新機能実装 - 投稿作成機能

次に、CRUDの「C」である投稿作成機能を実装していきます。

問題2: 投稿作成ページが404エラーになる!

エラー: 404 NOT FOUND
URL: http://127.0.0.1:8000/posts/create

原因:
ルーティング(routes/web.php)の 順序 が問題でした。

routes/web.php
// 間違った順序
// このルートが先に定義されていると...
Route::get('/posts/{id}', [HelloController::class, 'showPost']);

// ...'/posts/create' の "create" が {id} パラメータとして解釈されてしまう
Route::get('/posts/create', [HelloController::class, 'create']);

Laravelのルーターは、上から順にルート定義を評価します。そのため、動的なルート({id}のようなパラメータを含む)を先に書くと、createという文字列がIDとして扱われてしまい、createメソッドを呼び出すルートに到達できませんでした。

解決策:
具体的で固定的なルートを、動的なルートよりも先に定義 します。

routes/web.php
// 正しい順序
// 固定ルートを先に定義
Route::get('/posts/create', [HelloController::class, 'create'])->name('posts.create');
Route::post('/posts', [HelloController::class, 'store'])->name('posts.store');

// 動的ルートを後に定義
Route::get('/posts/{id}', [HelloController::class, 'showPost'])->name('posts.show');

実装の詳細

1. Controllerにcreatestoreメソッドを追加

HelloController.phpに、投稿フォーム表示用(create)と、データ保存用(store)の2つのメソッドを追加します。

app/http/controllers/HelloController.php
<?php

// ... (省略)
use Illuminate\Http\Request; // Requestクラスをインポート

class HelloController extends Controller
{
    // ... (既存のメソッドは省略) ...

    /**
     * 投稿作成フォームを表示する
     */
    public function create()
    {
        return view('create-post');
    }

    /**
     * フォームから送信されたデータをデータベースに保存する
     */
    public function store(Request $request)
    {
        // Eloquentを使ってデータを保存
        Post::create([
            'title' => $request->title,
            'content' => $request->content
        ]);
        
        // 保存後は投稿一覧ページにリダイレクト
        return redirect()->route('posts.index');
    }
}

storeメソッドのポイント

  • 引数 Request $request: フォームから送信された全てのデータ(タイトルや内容など)をオブジェクトとして受け取ります。
  • Post::create([...]): $fillableで許可されたカラムのデータを一括でデータベースに保存します。
  • redirect()->route('posts.index'): 処理完了後、名前付きルートposts.index(投稿一覧ページ)にユーザーをリダイレクトさせます。

2. 投稿作成フォームのビューを作成

resources/views/create-post.blade.php を新規作成します。

resources/views/create-post.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>新しい投稿を作成</title>
</head>
<body>
    <h1>新しい投稿を作成</h1>

    <form method="POST" action="{{ route('posts.store') }}">
        @csrf

        <div>
            <label for="title">タイトル:</label><br>
            <input type="text" id="title" name="title" required style="width: 300px; padding: 5px;">
        </div>
        <br>
        <div>
            <label for="content">内容:</label><br>
            <textarea id="content" name="content" required style="width: 300px; height: 100px; padding: 5px;"></textarea>
        </div>
        <br>
        <button type="submit" style="padding: 10px 20px; background: #007cba; color: white; border: none; cursor: pointer;">
            投稿する
        </button>
    </form>
    <br>
    <a href="{{ route('posts.index') }}">投稿一覧へ戻る</a>
</body>
</html>

フォームの重要ポイント

  • method="POST": データを「作成」するので、HTTPメソッドはPOSTを指定します。
  • action="{{ route('posts.store') }}": フォームの送信先を、先ほど定義した名前付きルートで指定します。これにより、将来URLが変更されてもビューを修正する必要がありません。
  • @csrf: CSRF(クロスサイトリクエストフォージェリ)攻撃 を防ぐための必須ディレクティブです。Laravelは自動的にトークンを生成し、不正なリクエストからアプリケーションを保護します。

データフローの全体像

ユーザーが投稿を作成するまでの一連の流れをまとめます。

1.【フォーム表示】
   ユーザーが GET /posts/create にアクセス
   => ルーターが HelloController@create を呼び出す
   => create-post.blade.php (投稿フォーム) を表示

2.【データ送信】
   ユーザーがフォームを入力し、「投稿する」ボタンをクリック
   => フォームデータが POST /posts に送信される

3.【データ保存】
   => ルーターが HelloController@store を呼び出す
   => storeメソッドがリクエストデータを受け取り、データベースに保存

4.【リダイレクト】
   => storeメソッドが redirect() で投稿一覧ページ (GET /posts) へのリダイレクトを指示

5.【一覧表示】
   => ルーターが HelloController@posts を呼び出す
   => posts.blade.php で、新しい投稿を含む一覧が表示される

このように、MVCパターンがそれぞれの役割を分担し、連携して一連の処理を実現しています。


達成したことと技術的な学び

今回実装した機能のURL一覧

HTTPメソッド URL アクション
GET /posts 投稿一覧の表示
GET /posts/create 投稿作成フォームの表示
POST /posts 新規投稿の保存
GET /posts/{id} 個別投稿の表示

技術的学習ポイント

  1. ルーティングの順序の重要性
    • 固定的なルート (/posts/create) は、動的なルート (/posts/{id}) より先に定義しないと意図通りに動作しないことを学びました。
  2. Eloquent ORMの便利なメソッド
    • ::all() (全件取得), ::findOrFail($id) (ID指定取得), ::create() (新規作成) といった基本的なCRUD操作をマスターしました。
  3. Bladeテンプレートとセキュリティ
    • {{ route(...) }} によるURL生成、@csrf によるセキュリティ対策、{{ }} によるXSS対策など、Bladeの強力な機能を活用できました。
  4. 基本的なセキュリティ対策
    • CSRF: @csrfディレクティブで対策。
    • SQLインジェクション: Eloquent ORMが自動的にプリペアドステートメントを使用するため、安全にDB操作ができます。
    • XSS: Bladeの{{ }}がデフォルトでHTMLをエスケープしてくれます。
    • マスアサインメント: Modelの$fillableプロパティで意図しないデータ更新を防ぎます。

コミットログ

今回の実装は、以下の内容でコミットしました。

feat: 投稿作成フォーム機能の実装

- 投稿作成フォームページ(/posts/create)を追加
- HelloControllerにcreate()とstore()メソッドを実装
- POST /postsルートでフォーム送信処理に対応
- ルーティング順序を修正 (createを{id}より先に定義)
- CSRFトークン付きフォームでセキュリティを確保

+ resources/views/create-post.blade.php
~ app/Http/Controllers/HelloController.php
~ routes/web.php

まとめ

プログラミング学習4日目は、LaravelにおけるCRUDの「C」と「R」を完全に実装しました。特にルーティングの順序でつまずいた経験は、フレームワークの挙動を深く理解する良い機会となりました。

  • 今回の達成目標
    • 投稿一覧表示(Read)
    • 個別投稿表示(Read)
    • 投稿作成フォーム(Create)
    • フォーム送信処理(Create)
    • 投稿編集機能(Update)※次回
    • 投稿削除機能(Delete)※次回

Webアプリケーション開発の基本的な流れと、MVCパターンがどのように連携するのかを、手を動かしながら体感できた非常に有意義な1日でした。


次回予告: LaravelでCRUD完全版!バリデーションと更新・削除機能の実装 - 5日目

明日は残りの「U(Update)」と「D(Delete)」機能に加え、不正な入力を防ぐための サーバーサイドバリデーション を実装し、CRUDアプリケーションを完成させます!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?