8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

初心者が説明する初心者のためのMVCフレームワーク

Posted at

はじめに

MVCフレームワークとは

  • アプリケーション開発における設計手法「MVCモデル(Model-View-Controller)」を基に作られたフレームワーク
  • フレームワークの代表的なものにRuby on Rails(Ruby) や Laravel(PHP)、Django(Python)などがあり、これら全てMVCフレームワークに分類される
  • 実際のMVCフレームワークでは、URLに応じたRouting(ルーティング)機能も提供されるが、Routing自体は純粋なMVCモデルには含まれない

MVCモデルとは

  • アプリケーションの設計を「Model(モデル)・View(ビュー)・Controller(コントローラー)」の3つに分けて整理するアーキテクチャ(設計の考え方)

MVCモデル概要

Image from Gyazo

名前 担当 役割の説明
Model(モデル) DB連携、ビジネスロジック※1 コントローラーからの指示を受けて、データベースとやり取りを行う
View(ビュー) ユーザーインターフェース コントローラーから渡されたデータを使って、ユーザーに見える部分(HTML等)を組み立て、表示する画面を生成
Controller(コントローラー) Webアプリケーションの司令塔 ルーティングで指定されたアクションを起点に、リクエストを受け取り、モデルを操作し、必要なデータをビューに渡す
Routing(ルーティング)※2 リクエストの振り分け処理 クライアントからのリクエストを受け取り、どのコントローラーのどのアクションに処理を任せるかを振り分ける

※1⋯アプリケーションが 現実の業務(ビジネス)をどのように処理するか を定義するルールや処理のこと
例えば
ユーザー登録のときメールアドレスがすでに使われていないかチェックする
商品の在庫がない場合は注文できないようにする

※2⋯RoutingはMVCの基本構成には含まれないが、実際のフレームワークでは重要な役割を果たす

開発順序

  1. アプリケーション仕様を決定
  2. モデル(データ設計)を作成
  3. 必要なルーティングを設計
  4. コントローラーを作成
  5. ビューを作成

上記の順序に沿って開発順序2から次項で解説する

※モデルとビューは機能によっていは不要な場合もある
※小規模・試作段階では、ルーティング→コントローラー→ビュー→モデル後付けの進め方も可

コード解説

実際のコードを例に処理について

みんなが自由に呟けるアプリ(スーパー簡易型Xみたいなアプリ)

目次

  1. ビューから受け取った入力値をモデルを使いDBに保存するロジックを作成
  2. ポストの新規作成のルーティングを作成
  3. Postコントローラー"create","store"メソッドを作成
  4. ユーザーに見える部分の作成

ユーザーからの入力値をモデルを使いDBに保存・データ設計

  • ビューの入力フォームから値(ユーザーからの入力)を受取、DBに保存する例
app\Models/Post.php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'body',
    ];

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }


    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

  • namespace App\Models;はルールに沿って名前空間を示す
  • Model はLaravelの Eloquent ORM(データベースとのやり取りを簡単にする仕組み)の基本クラス
  • HasFactory は、テストやデータ生成のために「ファクトリ」が使えるようになる機能
  • PostクラスはModelクラス(Eloquentモデル)を継承する
  • fillableはホワイトリスト方式で、どのカラムが「まとめて保存できるか」を指定する
    指定していないカラムは、リクエストからまとめて渡された場合に無視される
    (今回の場合、"title"と"body"のプロパティ以外は無視される)

commentメソッド

  • 「Postは複数のCommentを持つ」 という関係(1対多)を示している
  • ポストには複数のコメントがつく(1つのポストに複数のコメントを紐づける)

userメソッド

  • 「Postは1人のUserに属する」 という関係(多対1)を示している
  • 誰がこの投稿を作ったかを示す(複数のポストがありこのポストは登録ユーザの1人に紐づけるられる)
「投稿データ」を表現していてタイトルと本文だけをまとめて登録でき投稿は「1人のユーザー」に属していて「たくさんのコメント」を持つということ 

モデルでDBに保存する処理を書いていないのは親クラス(Modelクラス)に、「保存するための仕組み」がすでに用意されているから

ポストの新規作成のルーティングを作成

  • /post/createのURLにHTTPリクエスト(get)がクライアントから渡ってきた時の
    ルーティングのコーディング例
routes/web.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;

Route::get('post/create', [PostController::class, 'create'])->name('post.create');
Route::post('post', [PostController::class, 'store'])->name('post.store');

大前提として、新規投稿をポストする時は"create"新規作成フォームのページに行くルートと新規作成フォームから送信されたデータをDBに保存するルート2つのルートが必要

Route::get

クライアント(ユーザー)から、URI:"post/create"にHTTPリクエスト(get)が送られて来た時。PostController(クラス)の"create"メソッドで処理をする。ルートの名前は"post.create"と示す

「ルーティングからPostコントローラー(司令塔)に"create"メソッドで処理をお願いします」ということ

Route::post

ユーザーからURI:"post"にHTTPリクエスト(post)が送られて来た時にPostControllerの
"store"メソッドで処理する。ルートの名前は"post.store"と示す

「ルーティングからPostコントローラー(司令塔)に"store"メソッドで処理をお願いします」ということ

実際のソースコードでは、Route::resource('posts', PostController::class); のように
リソースコントローラーを使って、7つの基本ルート(index, create, store, show, edit, update, destroy)を一括で登録しているので、個別に get('post/create') を書く必要がない

Postコントローラー"create","store"メソッドを作成

  • ルーティングから受けたリクエストを処理をする"create"メソッドのコード例
app\Http\Controllers\PostController.php

namespace App\Http\Controllers;
use App\Models\Post;

class PostController extends Controller
{
    public function create()
    {
        $this->authorize('create', Post::class);
        return view('post.create');
    }
    
     public function store(Request $request)
    {
        $this->authorize('create', Post::class);
        $validated = $request->validate([
            'title' => 'required|max:20',
            'body' => 'required|max:400',
        ]);

        $post = new Post($validated);

        $post->user()->associate(auth()->user());
        $post->save();


        $request->session()->flash('message', '保存しました');
        return RedirectHelper::backWithPage($request, 'post.index', ['post' => $post]);
    }
}
app/Policies/PostPolicy.php
class PostPolicy
{
    public function create(User $user): bool
    {
        return auth()->check();
    }
}
  • namespace App\Http\Controllers;はルールに沿って名前空間を示す
    名前空間(namespace)とは⋯クラス名が被るのを防いだり、整理整頓するためのもの
  • PostControllerクラスはControllerクラスを継承する

PostPolicycreateメソッド

  • createメソッドでは、auth()->check() によって、現在ユーザーがログインしているかどうかを判定し、ログインしていればtrueを返し、認可を通過する。ログインしていない場合、認可が失敗する

$this->authorize('create', Post::class) の処理

  • $this->authorize() は、PostPolicy クラスの create メソッドを実行し、ユーザーがこの操作を実行する権限があるかどうかを確認する

"create"メソッドの処理

  • 共通の処理で認可されている場合にresources/views/post/create.blade.php という Bladeテンプレートを表示
    • ルーティングから渡されたリクエストに基づいて、ユーザーが操作できる画面(フォーム)を生成するためのもの
「ルーティングから渡ってきた処理を実行し、ビューにユーザーが見える部分を生成をお願いします」ということ

"store"メソッドの処理

  • バリデーションの処理
    • $request->validate([...]) は、ユーザーから送信されたデータをバリデーションします。もしバリデーションに失敗すると、エラーメッセージとともに元のフォームページにリダイレクトされる

  • 投稿の保存
    • バリデーション('title' => 'required|max:20','body' => 'required|max:400')に通ると、Post モデルに入力データが保存される。その後、$post->user()->associate(auth()->user()) で投稿とログイン中のユーザーを紐づけ、$post->save() でデータベースに保存する
    • $post->save() は、モデルが持つ保存処理を実行し、実際のデータベースへの挿入はEloquentによって行われる

$post->save()は、モデルをデータベースに保存するための処理を実行します。Eloquentが内部でSQLを生成し、実際にデータベースへの保存を行う


  • リダイレクトの処理
    • $request->session()->flash('message', '保存しました') でセッションにメッセージを保存し、その後 RedirectHelper::backWithPage($request, 'post.index', ['post' => $post]) を使って、投稿一覧ページ(post.index)にリダイレクトする
    • RedirectHelper::backWithPage は、指定されたページにリダイレクトし、ユーザーが元のページに戻るように処理するカスタム(自作)ヘルパー
「ユーザーから受け取った入力データを使って、ログイン中のユーザーと紐づく投稿を作ってDBに保存してねモデルさん」ということ

ユーザーに見える部分の作成

  • LaravelのBladeテンプレートファイルで「記事投稿画面(フォーム)」を表示する例
    LaravelのBladeテンプレートとは⋯Bladeテンプレートは、HTMLをベースにし、PHPのロジック(条件分岐やループなど)を簡単な記法で埋め込めるLaravel専用のテンプレートエンジン
create.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            記事投稿
        </h2>
    </x-slot>
    <div class="max-w-7xl mx-auto px-6">
        @if(session('message'))
            <div class="text-red-600 font-bold">
                {{session('message')}}
            </div>
        @endif
        <form method="post" action="{{ route('post.store') }}">
            @csrf
            <x-input-field name="title" label="件名" type="text" />

            <x-input-field name="body" label="本文" type="textarea" />

            <x-button class="mt-4">
                ポストする
            </x-button>
        </form>
    </div>

</x-app-layout>
  • クライアント(ユーザー)が "/post/create"にアクセスした際表示されるページ

    • <x-app-layout>⋯アプリケーションの共通レイアウトを適用するためのカスタムコンポーネント。このコンポーネントは、ヘッダーやサイドバー、フッターなどの共通部分を管理するために使用される。を使ってヘッダー部分をカスタマイズできる
    • </x-slot>⋯コンポーネントの中で指定したスロット(ここではheader)に内容を埋め込むための構文。このスロットに渡す内容は、コンポーネント内で定義された場所に表示される
    • <x-input-field><x-button> はカスタムコンポーネントで、フォーム入力欄やボタンの共通のスタイルを再利用可能にするため作成されている。これらのコンポーネントは resources/views/components 内に定義されている
  • @csrf⋯フォーム送信時にCSRFトークンを追加します。これにより、外部サイトからの不正リクエスト(クロスサイトリクエストフォージェリ)を防ぐことができ、セキュリティが強化される

  • formタグでは、ユーザーが入力する「件名」と「本文」のデータを受け取ります。

    • 入力欄のname属性に"title""body"が指定されているため、フォーム送信時に
      「件名」→name="title"  「本文」→name="body"  
      という形でサーバーにデータが送信される仕組みになっている
  • method="post"が指定されているのは、DBに新しいデータを追加・変更する場合には、HTTPメソッドのPOSTを使うのが基本ルール

  • actionでは route('post.store') を使って送信先URLを生成し、対応するルーティング([post.store])経由でコントローラーのstoreメソッドへ処理を渡す
    ※route()は、指定したルート名(ここでは'post.store')から対応するURLを生成するLaravelのヘルパー関数

まとめ

  • 本記事ではユーザーがURIにHTTPリクエストを送信して新規ポストを作成するフォームのページに移動し、ユーザーが入力したデータをDBに保存する処理について解説しました
  • ユーザー側の操作はページ移動とフォームの打ち込みの2つの作業のみですが、コンピュータ側では複数のコード(ファイル)を通じて処理が行われていることが確認できたと思います
  • ユーザーがリクエストしてウェブページが表示されるまでの流れと開発手順は以下のようにまとめます
  • 処理フロー
    • ユーザー → ルーティング → コントローラー → ビュー・モデル 
      という順序でリクエストに応じた処理が行われています
  • 開発のすすめかた
    • モデル → ルーティング → コントローラー → ビュー 
      という順序で開発すると効率よく開発することができます

※ 今回はDBにテーブルを作ったりカラム(列)を追加する処理については説明を省略している

なぜMVCモデルが多くのフレームワークで使われるのか

ひとことで言うと分かりやすく、直しやすく、使い回しやすく、バグを見つけやすいアプリが作れるから

  • MVC (モデル(Model)、ビュー(View)、コントローラー(Controller))設計することで下記のように責任を明確に分担でき、コードの保守性や再利用性が高まる
    • Model- データ管理(ビジネスロジックやデータベース操作)
    • View- 画面表示(UI、ユーザーに見える部分)
    • Controller- 入力処理(ユーザーの操作に応じてModelとViewをつなぐ)

なぜ保守性と再利用性が高まるのか??

  • 保守性UP
    例えば…
    • デザイン(View)を変えたい → ModelやControllerを変更する必要なし
    • データ処理(Model)を改良したい → 画面(View)を気にしなくていい
      つまり一部を改良しても他に影響を及ぼさない

  • 再利用性が高い
    例えば…
    • Model(データ処理部分)だけを使い回すことができる
    • Viewだけテーマを変えて別の見た目にすることができる
      つまり同じロジックを別の場面で使い回せるので、開発コストを抑えられる

最後に

私は未経験からプログラミング学習を始めて、もうすぐ2年が経ちます。
当初はPCの基本操作すら分からず、プログラミングに取り組む以前の状態でした。
何度も挫折しそうになり、逃げ出したくなることもありましたが、それでも毎日PCを開き、学習を続けました。
その積み重ねによって、少しずつではありますが、知識が身に付きました
今回は、そんな「なんとなく知っている」状態を「理解している」状態へ引き上げるため、
技術記事を書きました。
正直、簡単な作業ではなく、完成までに約4日間かかりました。
記事執筆の過程では、これまで自分が書いたコードや学習内容を何度も見直し、復習と新たなインプットを繰り返しました。
この経験を通して、なぜ記事を書くことで理解が深まるのか、プログラミング学習においてアウトプットがいかに重要か実感しました。

最後に、私のようにPC操作すらできなかった人間でも、2年間学び続ければ、ある程度理解できるようになり、プログラミングを楽しいと感じられるようになります。
だからこそ、どうか諦めず、学習を続けてほしいと思います。

最後まで記事を読んでいただきありがとうございます。

参考記事

8
2
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
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?