👨‍👧‍👦

【Laravel】 MVCの処理の流れ(簡単な実装例をもとに解説)

2022/10/16に公開約6,600字

Laravelのバージョン

$ php artisan -V
Laravel Framework 9.26.1

MVCの導入

まずは,MVCをそもそも聞いたことがない人のために,導入としてMVCを簡単に説明したいと思います.

この記事では,こちらの図を使って説明していきます.
MVC構造.jpg

MVCとは,UI(ユーザーインタフェース)を持つソフトウェアに適用されるアーキテクチャ(構造)の一種で,M(モデル),V(ビュー),C(コントローラ)に処理を分割して,ソフトウェアの内部データをユーザーが直接参照・編集できる情報から分離する構造のことを言います.

わかりづらいですね・・・
ということで,事前にいくつかの疑問を解消したうえで,実装していこうと思います.

UIって何?

ユーザーが直接参照・編集できる部分のことです(Webページなど).

MVCのそれぞれの役割は?

M(Model:モデル)
内部データ(データベース)にアクセスし,データの抽出・挿入などを行います.
V(View:ビュー)
渡されたデータをもとに,ユーザー側にUI(Webページなど)を提供します.
C(Controller:コントローラ)
モデルとビューに指示を送ります.
モデルへの指示:「どのデータにどのような操作をするのか」を指示する
ビューへの指示:「どのデータをどのビューへ渡し,表示するのか」を指示する

なぜ役割を分割するの?

ソフトウェアの規模が大きくなっても,保守性と可読性を高く保つためです.ここについては実装を見ていくとわかってくると思います.

簡単な実装例を通して考える

ここからは実際に簡単な実装例を通して,MVCの流れを見ていきます.
以下のように,投稿(Post)のタイトル(title)と本文(body)をWebページに一覧表示させます.

完成ページ
title1
body1

title2
body2

title3
body3

...(省略)...

また,Postテーブルは以下のような構成になっています(詳細は省略しています).

Post
id 投稿ID(PK)
title 投稿タイトル
body 投稿本文
crated_at 作成日時
updated_at 更新日時

ルーティング(web.php)

MVC構造.jpg

まずはルーティングから実装していきます.
ルーティングとは,UI(Webページ)で受け取ったリクエスト(URLなど)に対してどのような処理を行うかを定義する部分のことです.
Laravelではweb.phpというファイルにその定義を記述します.

/routes/web.php
<?php

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

Route::get('/index', [PostController::class, 'index']);

ここでRoute::get('/index', [PostController::class, 'index']);は,上部でuseしているRouteを用いて受け取った,/indexというURLのgetメソッドのリクエスト(図中➀)に対して,上部でuseしているPostControllerクラス内の,indexメソッドを実行する(図中➁)ということを定義しています.

Controller

MVC構造.jpg

ということで,次は呼び出されるPostControllerクラスと,その中にあるindexメソッドを定義しましょう.

/app/Http/Controllers/PostController.php
<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function index(Post $post)
    {
        return view('posts/index')->with(['posts' => $post->getOrderBy()]);
    }
}

こちらも一つずつ説明していきます.

namespace(名前空間)について

まず,namespaceについてですが,なぜこれを記述する必要があると思いますか?

・・・

先ほどweb.php内で,use App\Http\Controllers\PostController;という記述があったと思います.これはApp\Http\Controllersというディレクトリ構造の中にあるPostControllerクラスをこのファイルで利用しますという宣言です.

ここで,人間であればエディターからディレクトリ構造を確認し,App\Http\Controllersというディレクトリ構造の中にあるPostController.phpを簡単に見つけることができ,その中にPostControllerクラスがあることを突き止めることが出来ます.

ただ,これと同じことをコンピュータにやらせようと思ったら,あなたならどうするでしょうか?

・・・

このアプローチの一つがnamespaceです.日本語だと名前空間とも呼ばれますが,ファイル内に自身のディレクトリの相対位置を記述しておくことで,コンピュータはファイルを見つけることが出来るようになります.

続いて,Postクラスをuseしています.これは後ほど実装するモデルに処理をお願いするための宣言です.

extends(継承)について

その後,Controllerクラスをextends(継承)したPostControllerクラスを定義します.このextendsをすることで,Laravelが用意してくれたControllerクラスのメソッドを継承したうえでPostControllerクラスを拡張していくことが出来ます.

ここからやっとindexメソッドの説明に入ります.
まず引数に注目しましょう.(Post $post)となっています.これは上部でuseしたPostクラスを$postにインスタンス化しています.

インスタンス(実体)化について

ここで,インスタンス化とは「実際にたい焼きをつくること」だと考えてください.
クラスとは「たい焼き」を作る型です.たい焼きそのものではないので,中にあんこをいれたり,カスタードを入れたりすることはできません.
実際に食べる(使用する)ためには,その型を使って「実際にたい焼きをつくること」が必要です.これがインスタンス化の意味するところになります.


インスタンス化

ということで,返り値(return)の部分で実際に使用していきます.
まずは,「どのビューを表示させるのか」を定義しています.今回の場合だと,/resources/views/posts内にあるindex.blade.phpを表示させています(図中➂).

続いて,with以降の部分で「どのデータを渡すのか」を定義しています.今回の場合だと,$postgetOrderByメソッド(図中➃)を使用した結果をpostsという名前をつけて,index.blade.phpに渡しています.

このgetOrderByメソッドとは何でしょう?
これは,次のモデルで定義することになります.

Model

MVC構造.jpg

ということで,indexメソッド内で呼び出されたgetOrderByメソッドを実装していきましょう.

/app/Models/Post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{    
    public function getOrderBy()
    {
        return $this->orderBy('updated_at', 'DESC')->get();
    }
}

ここでの説明は,これまで説明してきたものと被る部分があるので,すこし省略して説明します.まず,namespaceを定義し,Modelクラスをuseします.
そして,そのModelクラスを継承した,Postクラスを定義し,その中で,getOrderByメソッドを定義しています.

thisについて

まず,$thisについて説明したいと思います.これは「このメソッドが所属するクラス(親クラス)のインスタンス」を表します.
つまり,この場合はPostクラスのインスタンス,PostController内で言うところの$postにあたります.

そして,その中にある,orderByメソッドと,getメソッドを順に使用しています(図中➄).
このorderByメソッドと,getメソッドはModelクラスに標準装備されているメソッドで,PostクラスがModelクラスをextendsしているため,使用することが出来ます.

ここで,orderBy('updated_at', 'DESC')$this内のデータを更新日時(updated_at)を基準に,降順(DESC)に並び替えるメソッドです.
そして,getメソッドはそのデータを抽出するメソッドです.

つまり,返り値の$thisには,更新日時を基準に,降順に並び替えられたPostモデル内のデータが全て格納されていることになります.

View

MVC構造.jpg

最後はindexメソッドの返り値で指定されていたindex.blade.phpを実装していきましょう.

/resources/views/posts/index.blade.php
<!DOCTYPE html> 
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        ...(省略)...
    </head>
    <body>
        <div class='posts'>
            @foreach ($posts as $post)
            <div class='post'>
                <h1 class='title'>{{ $post->title }}</h1>
                <p class='body'>{{ $post->body }}</p>
            </div>
            @endforeach
        </div>
    </body>
</html>

ここで注目すべきはpostsというクラス名が付けられたdivタグの中身です.@foreach ($posts as $post)の部分で,PostControllerで渡したposts$postsとして使用しているのが分かると思います

$postsforeachディレクティブというBladeファイルの機能により$postに一つずつ順番に格納され,postというクラス名が付けられたdivタグの中身が$posts内の投稿の個数分だけ繰り返されます(今回はメインのトピックではないのでこれ以上の説明は省略します).

ここまでの実装により,最初に示したWebページを表示することが出来ます.

まとめ(MVCとは)

MVC構造.jpg

以上の実装を通して,なぜこのようなアーキテクチャを採用しているかが,なんとなくわかっていただけたでしょうか?

この記事の実装はごく簡単なものでしたが,規模が大きくなるにつれて,コードの量は増えていきます.その際に,これまでのMVCの役割を全て一つのファイルが担っていたとしたらどうでしょう?

とても読みづらいし,メンテナンスなんて無理ですよね.

また,ユーザーが簡単にデータベースにアクセスできてしまうと,セキュリティ面で危険です.

以上のような理由から,UIを持つソフトウェア開発では,MVCの役割分担を守って実装していきましょう.

ただ,このMVC構造が最善の手段というわけではありません.人によってこだわりがあったり,そもそもMVC以外のアーキテクチャも存在します.また,今後よりよいアーキテクチャが開発される可能性もあります.

そのため,まずは基本を守ったうえで,様々な人・本・経験から学んで,自分自身で本当に良い実装というものを考え続けていくのが最も大切だと思います.

Discussion

ログインするとコメントできます