尋ねるな、命じよ。そして役割分担をしよう!

例として

会員情報の更新時に、条件に応じてメール送信する。

まずはこんな実装を考えてみましょう。

  1. コントローラー内でメール送信に該当するか判定する。
  2. 該当する場合は、メール送信用のサービスクラスを呼ぶ。
class UpdateMyInfoController extends Controller
{
    /**
     * 会員情報を更新
     */
    public function __invoke(
        UpdateMyInfoRequestData $requestData,
        UpdateMyInfoService $updateMyInfo,
        UpdateMyInfoMailService $updateMyInfoMail,
    ) {
		// 会員情報を更新
		$updateMyInfo->execute(
			$requestData,
		);

        // 条件に応じてメール送信
        if ($requestData->acceptMailMagazine) { // 👈 ここの条件分岐に注目
            $updateMyInfoMail->execute(
                $requestData,
            );
        }

		// レスポンス
        return response()->json();
    }
}

コントローラー内に条件分岐が実装されていることに注目してください。

設計思想は多々ありますし、そんなに悪くないです。メール送信条件も明確です。

私なりの工夫

一方で「メール送信の条件に該当するかどうかを判別するのは、コントローラーの役割りではない。」という視点で実装すると下記のようになります。

class UpdateMyInfoController extends Controller
{
    /**
     * 会員情報の更新
     */
    public function __invoke(
        UpdateMyInfoRequestData $requestData,
        UpdateMyInfoService $updateMyInfo,
        UpdateMyInfoMailService $updateMyInfoMail,
    ) {
		// 会員情報を更新
		$updateMyInfo->execute(
			$requestData,
		);

        // メール送信
        $updateMyInfoMail->execute(
            $requestData,
        );

		// レスポンス
        return response()->json();
    }
}

コントローラーは無条件にメール送信用のサービスクラスを実行します。

詳細なことは、メール送信用のサービスクラスに任せます。

将来の機能追加で、メール送信の条件がもっと複雑になるかも?
やはりそれは、コントローラーで判断することではなく、メール送信用のサービスクラスの役割りになります。

コントローラーからは尋ねない、(メールを送れと) 命じるだけです。

class UpdateMyInfoMailService
{
    public function execute(UpdateMyInfoRequestData $requestData): void
    {
        // 送信条件に一致しなければ何もしない
        if (! $requestData->acceptMailMagazine) {
            return;
        }

        Mail::to($requestData->email)
            ->send(new UpdateMyInfoMail);
    }
}

わずかなことですが、これによりコントローラーの責任がひとつ減り、適切な役割分担ができました。