🗂

Laravel サービスクラスを活用したメソッドの実装方法

2025/02/23に公開

はじめに

Laravelで開発を進めていると、コントローラーが肥大化する問題に直面することがあります。
このような場合、処理をサービスクラスに分離することで、可読性や保守性が向上します。

本記事では、コメント機能を題材にサービスクラスを活用した実装方法をご紹介します。
特に以下の課題を解決することを目的としています。

  • コントローラーの肥大化を防ぐ
  • テストしやすいコードを実現する
  • 再利用可能な処理を実装する

なぜサービスクラスを利用するのか?

サービスクラスを導入することで、以下のようなメリットがあります。

メリット

  • コントローラーがスリムになり、可読性が向上
  • ビジネスロジックを再利用可能にできる
    特に、ビジネスロジックが複雑になる場合は、サービスクラスを導入することで保守や拡張がしやすくなります。

実行環境

PHP 8.3
Laravel 11
MySQL 8.0
JavaScript (fetch API使用)

上記を使用してフォロー機能を実装していきます。
また、サイトはログインしなければ使用できない仕様になっています。

実装の全体像

今回実装するコメント機能は、以下の責務分担で進めます。

ファイル 役割
CommentController リクエスト処理・サービス呼び出し
CommentService ビジネスロジックの処理
Commentモデル データベース操作

1. サービスクラスの作成

フォルダ構造の準備

サービスクラスは通常、app/Services ディレクトリに配置します。

mkdir app/Services

サービスクラスの作成

以下のコマンドでファイルを作成します

touch app/Services/CommentService.php

CommentService.php

<?php

namespace App\Services;

use App\Models\Comment;
use Illuminate\Support\Facades\Auth;

class CommentService
{
    /*コメントを作成する*/
    public function createComment(int $materialId, string $content): Comment
    {
        return Comment::create([
            'material_id' => $materialId,
            'user_id' => Auth::id(),
            'content' => $content,
        ]);
    }

    /*特定の教材に関連するコメントを取得す*/
    public function getCommentsForMaterial(int $materialId)
    {
        return Comment::where('material_id', $materialId)
                      ->with('user.profile')
                      ->latest()
                      ->get();
    }
}

2.コントローラーからサービスを呼び出す

次に、CommentController からサービスクラスを呼び出すように変更します。
CommentController.php

<?php

namespace App\Http\Controllers;

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

class CommentController extends Controller
{
    protected $commentService;

    /*コンストラクタでサービスを注入*/
    public function __construct(CommentService $commentService)
    {
        $this->commentService = $commentService;
    }

    /*コメントを投稿する*/
    public function store(OriginalProductCommentRequest $request, Original_product $product)
    {
        // バリデーション
        $validatedData = $request->validated();

        // dd([$validatedData, $product->id]);

        $this->commentService->createComment($validatedData, $product->id);

        // リダイレクト(リロードしてコメントを反映)
        return redirect()->route('products.show', $product->id);
    }
}

ポイント解説

  • 依存注入: コンストラクタで CommentService を注入しています。

3. ビューの準備

original_productのモデルにコメントを取得するリレーションを記述します。
app/Models/Original_product.php

    public function comments()
    {
        return $this->hasMany(Original_product_comment::class, 'original_product_id');
    }

app/Http/Controllers/ProductController.php

    public function show(string $id)
    {
        // プロダクトをIDで取得し、関連するタグと画像も一緒に取得
        $product = Original_product::with([
            'technologies', 
            'images', 
            'profile', 
            'comments' => function ($query) {
                $query->orderBy('created_at', 'desc'); // 新しい順に並べ替え
            }, 
            'comments.user.profile'
        ])->findOrFail($id);
        
        
        // 現在のログインユーザーのプロフィールを取得
        $profile = auth()->user()->profile;

        
        return view('products.show', compact('product','profile'));
    }

show.blade.phpを編集してコメントを表示できるようにします
show.blade.php

<div class="comment-container">
            <div class="select-comment original-product-view">
                <p class="comment-count">{{ $product->comments->count() }}件のコメント</p>
                @foreach ($product->comments as $comment)
                    <div class="comment">
                        <div class="comment-left">
                            <a href="#" class="comment-user-image">
                                <!-- リファクタリングしてくださいね -->
                                <img class="comment-user-image" src="{{ asset('assets/material_images/user_profile_image.png') }}" alt="M">
                            </a>
                        </div>
                        <div class="comment-right">
                            <div class="comment-user-info">
                                <a href="#" class="comment-user-name">{!! nl2br(e($comment->user->profile->username)) !!}</a>
                                <p class="comment-date">{{ $comment->created_at }}</p>
                            </div>
                            <p class="comment-detail">{!! nl2br(e($comment->comment)) !!}</p>
                        </div>
                    </div>
                @endforeach
            </div>
            <div class="comment-form-container">
                <form class="comment-form" action="{{ route('comments.store', $product->id) }}" method="POST">
                    @csrf
                    <textarea name="original-product-comment" id="original-product-comment" placeholder="コメントを入力"></textarea>
                    <button class="original-product-comment-button" type="submit">コメントを送信</button>
                </form>
            </div>
        </div>

上記のように表示されます。

4. 実装後のメリット

サービスクラス導入の効果

  • コントローラーが簡潔になり、読みやすくなった
  • ビジネスロジックの再利用が容易になった
  • 単体テストが書きやすくなった

まとめ

本記事では、サービスクラスを活用したコメント機能の実装方法をご紹介しました。

サービスクラスを導入することで、以下の効果が得られます。

  • コードの可読性向上
  • 保守性・再利用性の向上
  • テストが容易になる
    今は個人開発で規模は大きくないですが、業務システムや大規模なアプリケーションでは、この構成が有効になると思います。

今後もコードを書くときは保守性・再利用性・運用性のことを考えてうまく使い分けていこうと思います。

Discussion