【Laravel入門】MVC + S Serviceクラスの書き方!

2024年6月23日Laravel,PHP

今回は【Laravel入門】サービスモデルの書き方!MVC + S 居ついての紹介です。

MVC + Sとは?

LaravelのMVC(Model-View-Controller) + Serviceの構成のアーキテクチャです。

各項目について説明していきますね。

M:Model

Mとは、「Model」のことを指します。
モデルは、データベースのテーブルやそれに関連するオブジェクトを表します。

V:View

Vとは、「View」のことを指します。
ユーザーに表示される部分です。
HTML、CSS、JavaScriptなどのフロントエンドのコードがここに含まれます。

C:Controller

Cとは、「Controller」のことを指します。
ユーザーのリクエストを受け取り、モデルを操作してデータを取得し、ビューを表示するための処理を行います。
コントローラはアプリケーションのロジックを含み、モデルとビューの間の仲介役として機能します。

S:Service

MVCパターンに加えて、Laravelでは「Service」と呼ばれるレイヤーが追加して、アーキテクチャを組むことがあります。
これはビジネスロジックを格納するためのレイヤーであり、コントローラとのやりとりがメインになってきます。
これにより、コントローラが過剰に肥大化することなく、アプリケーションのロジックをよりきれいに分離することができます。

もう少し、砕いて説明しますね。

データベースのデータについてはModelが担当でしたよね?
Modelとのやり取りを行うコードを、Contrller内でデータベース関連のQuery文など書くと、
Controllerの記述が多くなり、負荷ボリュームも増え、コードの可読性が下がります。

ですが、ビジネスロジック部分だけを切り離せば、どうでしょうか?

つまり、Controllerにはデータベースとのビジネスロジック書かなければということです。

Controller以外で、細かいQueryなどの処理を記述すば、Controllerはそのロジックを使えばいいだけですよね?
それで登場するのが、「Service」クラスになります。

Serviceクラスに、データベースのビジネスロジックを記述して結果を返します。
そうすることで、ControllerはServiceクラスを呼び出して、その結果に対して、プログラム処理を書いていけばいいだけになります。

Modelの例

モデルクラスのサンプルコードです

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Notifiable;

/**
 * ユーザー情報のサービスクラス
 *
 */
class User extends Authenticatable
{
    use HasFactory, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

スポンサードサーチ

Serviceの例

サービスクラスのサンプルコードです。

icon

モデルとやり取りを行います。

<?php

namespace App\Services;

use App\Models\User;

class UserService
{
    /**
     * 新しいユーザーを作成します。
     *
     * @param array $data
     * @return User
     */
    public function createUser(array $data)
    {
        // ユーザーの作成と保存
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }

    /**
     * 指定されたユーザーを更新します。
     *
     * @param User $user
     * @param array $data
     * @return User
     */
    public function updateUser(User $user, array $data)
    {
        // ユーザーの更新と保存
        $user->update([
            'name' => $data['name'],
            'email' => $data['email'],
        ]);

        return $user;
    }

    /**
     * 指定されたユーザーを削除します。
     *
     * @param User $user
     * @return void
     */
    public function deleteUser(User $user)
    {
        // ユーザーの削除
        $user->delete();
    }

    /**
     * IDによるユーザーの取得
     *
     * @param int $id
     * @return User|null
     */
    public function getUserById($id)
    {
        // IDによるユーザーの取得
        return User::find($id);
    }

    /**
     * 全てのユーザー情報を取得
     *
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function getAllUsers()
    {
        // 全てのユーザー情報を取得
        return User::all();
    }
}

Controllerの例

icon

コントローラーのサンプルコードです

<?php

namespace App\Http\Controllers;

use App\Services\UserService;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /** @var UserService $userService */
    protected $userService;

    /**
     * UserControllerのインスタンスを作成します。
     *
     * @param UserService $userService
     */
    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    /**
     * 全てのユーザーを表示します。
     *
     * @return \Illuminate\View\View
     */
    public function index()
    {
        $users = $this->userService->getAllUsers();
        return view('users.index', compact('users'));
    }

    /**
     * 新しいユーザー作成フォームを表示します。
     *
     * @return \Illuminate\View\View
     */
    public function create()
    {
        return view('users.create');
    }

    /**
     * 新しいユーザーを保存します。
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(Request $request)
    {
        $userData = $request->only(['name', 'email', 'password']);
        $this->userService->createUser($userData);
        return redirect()->route('users.index')->with('success', 'User created successfully');
    }

    /**
     * 指定されたユーザーを表示します。
     *
     * @param int $id
     * @return \Illuminate\View\View
     */
    public function show($id)
    {
        $user = $this->userService->getUserById($id);

        if (!$user) {
            return redirect()->route('users.index')->with('error', 'User not found');
        }

        return view('users.show', compact('user'));
    }

    /**
     * 指定されたユーザーの編集フォームを表示します。
     *
     * @param int $id
     * @return \Illuminate\View\View
     */
    public function edit($id)
    {
        $user = $this->userService->getUserById($id);

        if (!$user) {
            return redirect()->route('users.index')->with('error', 'User not found');
        }

        return view('users.edit', compact('user'));
    }

    /**
     * 指定されたユーザーを更新します。
     *
     * @param Request $request
     * @param int $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(Request $request, $id)
    {
        $userData = $request->only(['name', 'email']);
        $user = $this->userService->getUserById($id);

        if (!$user) {
            return redirect()->route('users.index')->with('error', 'User not found');
        }

        $this->userService->updateUser($user, $userData);
        return redirect()->route('users.index')->with('success', 'User updated successfully');
    }

    /**
     * 指定されたユーザーを削除します。
     *
     * @param int $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy($id)
    {
        $user = $this->userService->getUserById($id);

        if (!$user) {
            return redirect()->route('users.index')->with('error', 'User not found');
        }

        $this->userService->deleteUser($user);
        return redirect()->route('users.index')->with('success', 'User deleted successfully');
    }
}

スポンサードサーチ

Viewの例

icon

ビューは用途に合わせて、ビュー毎に記述しています。

ユーザーの一覧表示用ビュー (index.blade.php)

ユーザーの一覧表示用ビューのビューです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Users List</title>
</head>
<body>
    <h1>Users List</h1>

    <!-- 成功メッセージを表示 -->
    @if(session('success'))
        <div style="color: green;">{{ session('success') }}</div>
    @endif

    <!-- エラーメッセージを表示 -->
    @if(session('error'))
        <div style="color: red;">{{ session('error') }}</div>
    @endif

    <!-- 新しいユーザー作成ページへのリンク -->
    <a href="{{ route('users.create') }}">Create New User</a>

    <table border="1">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <!-- ユーザーリストを表示 -->
            @foreach($users as $user)
                <tr>
                    <td>{{ $user->id }}</td>
                    <td>{{ $user->name }}</td>
                    <td>{{ $user->email }}</td>
                    <td>
                        <!-- ユーザー編集ページへのリンク -->
                        <a href="{{ route('users.edit', $user->id) }}">Edit</a>
                        <!-- ユーザー削除フォーム -->
                        <form action="{{ route('users.destroy', $user->id) }}" method="POST" style="display:inline;">
                            @csrf
                            @method('DELETE')
                            <button type="submit" onclick="return confirm('Are you sure?')">Delete</button>
                        </form>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
</body>
</html>

ユーザー作成用ビュー (create.blade.php):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Create User</title>
</head>
<body>
    <h1>Create User</h1>

    <!-- 新しいユーザー作成フォーム -->
    <form action="{{ route('users.store') }}" method="POST">
        @csrf
        <label for="name">Name:</label><br>
        <input type="text" id="name" name="name"><br>

        <label for="email">Email:</label><br>
        <input type="email" id="email" name="email"><br>

        <label for="password">Password:</label><br>
        <input type="password" id="password" name="password"><br>

        <button type="submit">Create</button>
    </form>
</body>
</html>

ユーザーの更新用ビュー (edit.blade.php)

icon

ユーザーの更新用のビューです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Edit User</title>
</head>
<body>
    <h1>Edit User</h1>

    <!-- ユーザー編集フォーム -->
    <form action="{{ route('users.update', $user->id) }}" method="POST">
        @csrf
        @method('PUT')

        <label for="name">Name:</label><br>
        <input type="text" id="name" name="name" value="{{ $user->name }}"><br>

        <label for="email">Email:</label><br>
        <input type="email" id="email" name="email" value="{{ $user->email }}"><br>

        <button type="submit">Update</button>
    </form>
</body>
</html>

ルーティング

ルーティングを「routes/web.php」に記述します。
ルーティングを記述しなければ、各メソッドは動かないので、実装しながら確認していきましょう。

use App\Http\Controllers\UserController;

Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
Route::post('/users', [UserController::class, 'store'])->name('users.store');
Route::get('/users/{id}', [UserController::class, 'show'])->name('users.show');
Route::get('/users/{id}/edit', [UserController::class, 'edit'])->name('users.edit');
Route::put('/users/{id}', [UserController::class, 'update'])->name('users.update');
Route::delete('/users/{id}', [UserController::class, 'destroy'])->name('users.destroy');

Laravel,PHPLaravel

Posted by kami