🥣

Laravelのオブジェクト指向設計の基本ざっくり整理してみた

2025/03/07に公開

自分用にまとめたものなりますが🙇

目的

コーディングする際にどのようにクラスを分け、管理すればよいかを迷わなくなるようにする。

FatControllerの欠点

MVCアーキテクチャ

・Model:リソースへの接続を担当
・View:表示・入出力の処理を担当
・Controller:ModelとViewの間を取り持つ

参考:https://martinfowler.com/eaaCatalog/modelViewController.html

FatController

処理を詰め込みすぎて肥えてしまったコントローラー

class SampleController extends BaseController
{
    public function process(Request $request)
    {
        Authを使ってログインユーザーを取得
        Requestの値を見てRequestを整形
        Validatorを使ってvalidation
        DB1から情報を取得
        DB1から取得した情報を整形
        DB2から関連する情報を取得
        DB2から取得した情報を整形
        データを保存
        Mailを送信
        リダイレクト処理
    }
}

Fat Controllerの3つの欠点

Understandability(分かりやすさ)

結局何の処理をしているのかが理解しにくい。
上の例では何かをDBに保存していることは想像できるが、Requestを整形したりメールを送信したりすることは、実際の処理を追わないと把握できない。 
 → プログラマーの生産性を下げてしまう。

Reusability(再利用のしやすさ)

Controllerのアクションメソッド内に記述されたロジックは、他の箇所で再利用することが難しい。再利用できないと、他の箇所で同じロジックを組まなければならなくなる。 
 → 整合性の低さにつながる。

Changeability(変更のしやすさ)

コードを変更する時、既存のプログラムの正確性を確実に保てていると、変更に強いプログラムと言える。多くの処理を担ってしまうと、変更時に全ての処理に影響してしまうため、バグの発生率が上がってしまう。 
 → 保守性の低さにつながる。

FatControllerを避けるため、ビジネスロジックは「Component」や「Helper」や「Model」に切り分ける。

ComponentとHelperの使い方

Component

共通処理を機能毎にファイル分けして管理されていたり、「インターフェース」「抽象クラス」「トレイト」なども管理している。
インターフェース実装したクラスに制約を持たせる。実装を強制することで実装漏れを防ぐことができ、クラス同士の依存をなくすことで疎結合を実現できる。

interface TaskInterface
{
    public function formatData($data);
    public function calculateValue($value);

    public function executeProcess(Handler $handler, Log $log, Request $input);
}

↓使用例

class TaskProcessor implements TaskInterface
{
    public function __construct()
    {
        ・・・
    }

    public function formatData($data)
    {
        ・・・
    }

    public function calculateValue($value)
    {
        ・・・
    }

    public function executeProcess(Handler $handler, Log $log, Request $input)
    {
         ・・・
    }
}

⭐️ポイント:クラス名の後に「implements」を記載し、インターフェース内で定義されているメソッドは必ずクラス側でも定義する。

抽象クラス

インターフェースとの主な違いは、プロパティや具体的な処理が入るか否か。(オブジェクト指向)
共通処理は具体的に定義し、動きが変わる部分のみ抽象メソッドで定義することができる。
共通メソッドに変更を加えた場合は、継承先クラス全てに影響が及ぶ。

abstract class CouponBase
{
    protected $app;
    protected $config;
 
    public function __construct($app, $config)
    {
        $this->app = $app;
        $this->config = $config;
    }
 
    abstract public function targetCoupon(User $user);
    abstract public function grantCoupon(User $user);
}

↓使用例

class PremiumCoupon extends CouponBase
{
    public function targetCoupon(User $user)
    {
        ・・・
    }
 
    public function grantCoupon(User $user)
    {
         ・・・
    }
 
         ・
         ・
}

⭐️ポイント:クラス名の後に「extends」を記載し、抽象メソッドは必ず定義する。子クラスから親クラスのメソッドへアクセスできる(privateメソッド以外)。

トレイト

多重継承のような振る舞いが可能。
「use トレイト名」と書くだけで、クラス内でトレイトの機能を使えるようになる。
クラスとしてではなく機能のみをパーツとして使い回したい時に有効。
また、Interfaceと一緒に使うことでデフォルトの機能を実装することが可能。

trait TaskTrait
{
    public function checkValidation(Schedule $schedule, User $user, Collection $plans)
    {
        ・・・
        return $results;
   }
}

interface TaskValidatorInterface
{
    public function checkValidation(Schedule $schedule, User $user, Collection $plans);
    public function saveData(Request $input);
}

↓ 使用例

class TempTask implements TaskValidatorInterface
{
    use TaskTrait;

    public function saveData(Request $input)
    {
        $validation = $this->checkValidation($schedule, Auth::user(), $plans);
    }
}

⭐️ポイント:インターフェースにより制約があるにも関わらず、クラス内でcheckValidation()メソッドが定義されていないが、トレイトで実装されているためエラーとならない。
「use TaskTrait」を記載するだけで、$this->checkValidation()として機能を使うことができる。

Helper

static(静的)メソッドが機能ごとにファイル分けされている。
staticメソッドはインスタンスを生成せずに、クラスを直接指定し呼び出すことができる。
インスタンスごとの変更を伴わない、静的なメソッドを管理する。

class CouponHelper
{
    public static function setupCoupon(Coupon $coupon)
    {
        ・
        ・
 
        return new Coupon([・・・])
    }
 
        ・
        ・
}

↓ 使用例

class InitialCoupon extends CouponBase
{
     public function grantCoupon(User $user)
    {
        ・
        ・
 
        $user_coupon = CouponHelper::setupCoupon($coupon);
 
        ・
        ・
    }
 
        ・
        ・
}

⭐️ポイント:Helperクラスではstaticメソッドで定義されており、インスタンス化せずに静的に呼び出している。
特定の目的があるプロパティやメソッドをまとめる場合に使うイメージ。

Traitとの使い分け

staticメソッドはクラスに関連するインスタンスを返すべきとされている。
例えば、クーポンに関する計算結果を返す処理であればCouponTraitに定義し、計算結果を適用したユーザークーポンを返す処理であればCouponHelperに定義する。

Discussion