質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.09%

MVCのコントローラの役割が理解出来ていない

受付中

回答 1

投稿

  • 評価
  • クリップ 2
  • VIEW 2,212

score 15

前提

Laravelを使用してwebアプリを作成しています。
恥ずかしながらいつもなんとなくでクラスやロジックの追加修正やなどを行っており、動作はするけどクラス構成やそのクラスの責務が微妙だなあと思うことがよく発生します。

そこで、この場合、みなさんならどう作るのか、もしくはどう作るべきかなどを教えてほしいです。
ただの好みや間違いの指摘でも構いませんので、色々聞ければと思います。

 コード

決済を行う処理が有り、流れはだいたい下記の様になっています。

class App\Http\Controllers\MyController
{
// フォームからのPOST(決済実行)を受け付ける
public function postCheckout(App\Http\Requests\MyRequest $req)
{
    // リクエストの基本的なvalidateは実施済みのため、ここでのvalidateは不要
    try {
        // リクエストを基に、CheckoutのAPIに投げるデータを作成する
        $data = $this->makeDataForCheckout($req); // <- (1)
        // 決済用POSTデータで、決済を実行する
        $result = $this->checkout($data); // <- (2)
        if ($result->status !== 'succeeded') {
            // 決済失敗してる
            throw new Exception($result);
        }
        // 決済が完了した
        return redirect('/checkout/complete');
    } catch (Exception $e) {
        // 決済
        Log::error($e);
    }

    // 決済失敗してる
    return back()->withInput();
}
  1. リクエストを基に、CheckoutのAPIに投げるデータを作成する
  2. 決済用POSTデータで、決済を実行する
// 1. リクエストを基に、CheckoutのAPIに投げるデータを作成する
public function makeDataForCheckout(App\Http\Requests\MyRequest $req)
{
    // 金額含む商品情報と、決済ユーザ情報を取得し返す
    // 決済会社ごとに受け付けるフォーマットが違うため、考慮の必要有り
    return [
        'item' => Item::find($req->item_id),
        'user' => User::find($req->user_id),
    ];
}

// 2. 決済用POSTデータで、決済を実行する
public function checkout(array $data)
{
    // 例えばpaypal決済を実行する
    //  -> paypalクラスはただのラッパークラス
    return $paypal->checkout($data);
}

私が気になっているところですが、これらの1. 2.のメソッドをMyControllerクラスで持つべきなのか?という点です。

この場合、現状のままだと決済会社が増えるたびにコントローラが膨らんでいきます。
ただ決済会社を増やした場合、各会社ごと受け付けるデータが違うため、どこかしらでメソッドは増やす必要があります。

数バージョン考えてみました。良いと思える順です。

// 1. データの変換は全てDataConverterクラスで行う
// DataConverterは常にItemクラスとUserクラスに依存する
$data = $paypalDataConverter->makeData($req);
$result = $paypal->checkout($data);
// -> 決済会社が増えた場合、DataConverterクラスと決済会社クラスの2つを作成する

// 2. $reqをそのままPaypalクラスに渡してチェックアウトする
// paypalクラスのcheckoutメソッド内で データ変換を行う($paypalDataConverter->makeData($req) などの形)
$result = $paypal->checkout($req);

// 3. Itemクラスで決済まで行う
// paypalクラスやデータ変換クラスなどへの依存が生まれるので違和感
$result = Item::paypalCheckout($req);

もやっとしていますが、どうするべきかご指南頂けますと幸甚です。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

+3

それらは全てModelの役割です。

ControllerはURLに従って入力されたデータをModelに丸投げして値を受け取り、
Viewに受け取ったデータを丸投げするくらいしか書く内容はありません。
Modelにバリデートを依頼して、falseが返ってきたら400番エラーを返してリダイレクトするくらいの事は書いても良いでしょう。

ここは窓口Modelクラスを用意して対応しましょう。
また、オンライン決済インタフェースを用意しておきます。

私が作ると仮定してちょっと作戦を書いてみました。
下記のような感じの実装になると思います。
(interfaceとclassは1つにつき1ファイルで管理する)

interface onlinePayment
{
  public function isValid();
  public function checkout();
}

// こんな感じのラッパークラスを沢山用意
class paypalPayment implements onlinePayment
{
  public $req = null;

  function __construct($req){
    $this->req = $req;
  }

  public function isValid() {
    $result = false;
    // バリデートを実行
    return $result;
  }

  // スタティックなのはテストをしやすくする目的です。
  public static function convertFrom($req) {
    $data = null
    // $data を生成する処理
    return $data;
  }

  public function checkout() {
    $data = self::convertFrom($this->req);
    // 決済を実行して、失敗なら下記を投げる(このクラスはExceptionを投げて死ぬだけでOK)
    throw new Exception($result);
  }
}

// 窓口クラス
class paymentWindow implements onlinePayment 
{
  public $payments = [
    "paypal" => 'paypalPayment',
  ];

  function __construct($req){
    $payment = $this->payments[$req['target']];
    $this->payment = new $payment($req);
  }

  public function isValid() {
    return $this->payment->isValid();
  }

  public function checkout() {
    return $this->payment->checkout();
  }
}

空でバーっと書いたのでSyntaxエラーが出るかもしれませんが、一通り出来る方だと思いますので問題ないと判断しました。
窓口クラスが若干重いですが、結局は何処かに書いてメンテナンスしないといけないので、こんな所ですかね?
まぁ、案の1個くらいに思ってください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/23 08:20

    @miyabi-sun
    ありがとうございます!これだけでもとても勉強になります!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.09%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る