UseCaseクラスを用いてFatControllerを解消する

LaravelのFat Controllerを解消するため、UseCaseクラスに処理を分離するリファクタリング手法。

Laravel UseCase PHP

概要

MVCでアプリケーションを作ってるいると、Controller部分のコードが肥大化してしまうことがあります。ここではContollerではデータの処理などを行わずに、UseCaseクラスに処理を任せる例を紹介します。

Controllerでの処理をUseCaseクラスに任せる

ここでは例として、記事に紐づくタグの更新処理をUseCaseクラスに任せます。

Usecaseクラス導入前のコントローラ内の関数

関数内に記事に紐づくタグの更新処理が記述されていてスッキリしません。

   public function update(Request $request, Article $article)
    {
        $article->fill($request->only('title', 'body', 'mdbody', 'state'))->save();

        $article_id = $article->id;
        $pre_tag_names = $article->tags()->pluck('name')->toArray();
        $new_tag_names = $request->tags[0] == null ? [] : $request->tags;

        //create Tag
        foreach ($new_tag_names as $tag_name) {
            Tag::firstOrCreate(['name' => $tag_name]);
        }
        //update articletag
        $add_tag_names = array_diff($new_tag_names, $pre_tag_names);
        $add_tag_ids = Tag::whereIn('name', $add_tag_names)->pluck('id');
        foreach ($add_tag_ids as $tag_id) {
            ArticleTag::firstOrCreate(['article_id' => $article_id, 'tag_id' => $tag_id]);
        }
        $delete_tag_names = array_diff($pre_tag_names, $new_tag_names);
        $delete_tag_ids = Tag::whereIn('name', $delete_tag_names)->pluck('id');
        ArticleTag::where('article_id', $article_id)->whereIn('tag_id', $delete_tag_ids)->delete();

        return redirect(route('admin.article.manage'));
    }

Usecaseクラスでのタグの更新処理

新たにapp\\Usecases\\Tag\\UpdateArticleTags.phpを作成し、タグの更新処理を記述します。作成時にはpsr-4に従って、composer.jsonでautoloadを登録していないApp以外は、ファイルパスとnamespaseを大文字小文字も含めて一致させてください。 また、phpのマジックメソッドである __invokeメソッドはオブジェクトを関数として呼び出した際に呼び出される関数です。

<?php

namespace App\\Usecases\\Tag;

use App\\ArticleTag;
use App\\Tag;

class UpdateArticleTags
{
    public function __invoke($article_id = null, $pre_tag_names = [], $new_tag_names = [])
    {
        //create Tag
        foreach ($new_tag_names as $tag_name) {
            Tag::firstOrCreate(['name' => $tag_name]);
        }
        //update articletag
        $add_tag_names = array_diff($new_tag_names, $pre_tag_names);
        $add_tag_ids = Tag::whereIn('name', $add_tag_names)->pluck('id');
        foreach ($add_tag_ids as $tag_id) {
            ArticleTag::firstOrCreate(['article_id' => $article_id, 'tag_id' => $tag_id]);
        }
        $delete_tag_names = array_diff($pre_tag_names, $new_tag_names);
        $delete_tag_ids = Tag::whereIn('name', $delete_tag_names)->pluck('id');
        ArticleTag::where('article_id', $article_id)->whereIn('tag_id', $delete_tag_ids)->delete();
        return true;
    }
}

上のコードだと、

$obj = new UpdateArticleTags;
$obj($article_id,$pre_tag_names,$new_tag_names);

でUsecaseの__invoke関数を呼び出せます。

Controller内でUsecaseクラスを利用

タグの更新処理が一行になったので大分スッキリしました。

use App\\Article;
use Illuminate\\Http\\Request;
use App\\Usecases\\Tag\\UpdateArticleTags;

~~~

    public function update(Request $request, Article $article, UpdateArticleTags $updateTag)
    {
        $article->fill($request->only('title', 'body', 'mdbody', 'state'))->save();
        $updateTag($article->id, $article->tags()->pluck('name')->toArray(), $request->tags[0] == null ? [] : $request->tags);
        return redirect(route('admin.article.manage'));
    }

比較

usecase.png