がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

Model、どうすっかねぇ? 的な

直近思案しているのはLaravel5.5案件なのですが。
まぁ割と「あちこちのPHP MVCフレームワークで言える(ような気がする)」ので、あちこちに疑問を投げかける的な想定で。
端的には「データの入力やvalidateの処理、Modelに書きますか? Controllerに書きますか?」的な内容です。


さて。いわゆるMVCの「Model」ですが。
本来的には
・アプリケーションデータ、ビジネスルール、ロジック:システムの本体
をつかさどる、とされています。
言い方を変えると「どのModelを使うかの制御(Controller)、表示や出力(View)」以外全部、をつかさどる、と言い伝えられています。


ただまぁ、どこのRoRが元凶とかActive Record以下検閲削除とかは言いませんが、半歩間違えると、Modelって
・ORマッパーのことでしょ?
的なお話になることも、少なからずあるように思われます。


いや別に「Model === ORM」だ、ってんならそれはそれでよいのですが。
その場合はまぁ「Controllerがfatになるのは避けられないよねぇ」とか思うわけです。
いやもちろん「一旦処理を外に書き出して"Controllerというファイル名のファイルの中身は細くする"」ってことも可能ですが、それって本当に「not Fat Controllerなの?」とか思うわけです。
「書き出したクラス」って、所属はController? Model?
「ControllerでもModelでもないそれ以外」だとしたら、それは本当にMVC? とかとか。


あぁ念のため「おいちゃん的には、トップハム・ハット卿 *1 を否定している」わけではありません。
いやまぁ肯定もとくにせんのですが。
その辺はぶっちゃけると「どっちでもいいんじゃないかなぁ一貫性があれば」。


正直、ある程度の規模のシステムを組めば
・太った子が出てくるか
・やたら大量の子だくさんになるか
のどちらかはどちらにしても不可避なので。ゆえに「ある程度納得できる理由と芯の通った哲学」があればよいのではないかなぁ、と思うのですます。


んで。「どっちでもよい」ので、一旦、仮定として「例えば、トップハム・ハット卿を避ける方向」で考えた場合。
Modelですが。「ビジネスロジック全部まるっと」はちぃと難しそうな気がせんでもないのですが、「あるデータの塊に対する責務全般」くらいは、お願いしてもよいのではないかなぁ? とか、おいちゃん個人としては思うのでございます。
ほら「Tell, Don't Ask」とか言うじゃないですかまぁ一般的なModelのコードもそんな感じだとは思うのですが。とはいえ、もうちょっと「いろいろ、Modelに持ち込んでもよいのかなぁ」とか、なんとか。


で、ちょいとちょうど良い感じがあったので、さっそくの実例。
ちと書いてもらったコードがあったので、微妙に細部を変えながら、転記。

    public function postTweet(Request $request)
    {
        $userId = auth()->id();
        if (null === $userId) {
            return redirect('/');
        }
        // ここはもうちょっと「$request->validate」つかうのが本当なんだろうと思う
        $tweetPost = $request->input('tweet');
        if (144 < strlen($tweetPost)) {
            return redirect('/');
        }

        $tweetModel = new Tweet();

        $tweetModel->user_id = $userId;
        $tweetModel->tweet = $tweetPost;
        $tweetModel->save();

        Session::flash('message', 'tweet した');
        return redirect('/');
    }

うんたぶん、LaravelでなくほかのPHP MVCフレームワークであっても、大体こんな感じだろうなぁと思うですサンプルコード見ている限り。
別の場所をグぐってみても。
http://libro.tuyano.com/index3?id=7896003&page=3

public function postNew(Request $request)
{
    $name = $request->input('name');
    $mail = $request->input('mail');
    $age = $request->input('age');
    $data = array(
        'name' => $name,
        'mail' => $mail,
        'age' => $age
    );
    MyTable::create($data);
    return redirect()->action('HeloController@getIndex');
}

https://qiita.com/yagi21/items/eea131ef0d3bc20be59a

  public function res(Request $request){
    //フォームから受け取る
    $フォームのname = $request->input('フォームのname');
      .
      .
      .
    //DB保存
  }

などなど。
公式も
https://readouble.com/laravel/5.5/ja/eloquent.html

class FlightController extends Controller
{
    /**
     * 新しいflightインスタンスの生成
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // リクエストのバリデート処理…

        $flight = new Flight;

        $flight->name = $request->name;

        $flight->save();
    }
}

なので、Laravel公式的にも「処理はControllerに書く」のほうに倒れてるんだろうなぁ、とは思うです。
さて上述に共通するのは「Controllerにデータ取得とvalidateの処理が書いてある」あたり。


んで、おいちゃんが気になっているのが
・入力データの取得
・validate
の処理を「Controllerでやっている」あたり。
その辺「どうなんだろう??」と思うですだす。


で、一案っつか二案なんだが。
上述の処理を「model側に移す」のって、みんなの印象値的にどうなんだろう? という、質問ですます。
1つは「全部まとめて1メソッド」、もう1つは「入力、validate、保存の3メソッド」の提案。
まぁModel内部的にはどちらにしても切り分けるんだろうけど。


1つまとめの場合は身もふたもなくて

    public function postTweet(Request $request)
    {
        $tweetModel = new Tweet();
        $r = $tweetModel->insert用に入力してvalidateして保存();
        if (true === $r) {
            Session::flash('message', 'tweet した');
        }
        return redirect('/');
    }


3つの場合は

    public function postTweet(Request $request)
    {
        $tweetModel = new Tweet();
        $r = $tweetModel->insert用の入力();
        if (false === $r) {
            return redirect('/');
        }
        $r = $tweetModel->insert用のvalidate();
        if (false === $r) {
            return redirect('/');
        }
        $r = $tweetModel->insert保存();
        if (true === $r) {
            Session::flash('message', 'tweet した');
        }
        return redirect('/');
    }


なんかifがうざったいから、例外投げるほうが楽かもしんまい。

    public function postTweet(Request $request)
    {
        try {
            $tweetModel = new Tweet();
            $tweetModel->insert用の入力();
            $tweetModel->insert用のvalidate();
            $tweetModel->insert保存();
        } catch(Throwable $e) {
            return redirect('/');
        }

        Session::flash('message', 'tweet した');
        return redirect('/');
    }

こんな感じ。
細かい話をすると「insertとupdate」って、似てるけど処理が違うので。データ取り込みもvalidateも保存する時も、微妙に入り口を分けていたい感じではある。
ので、全体的に「insert用の」ってつけてるの感じ。


Modelのほうはまぁだいたいイメージがつくと思うので適宜オミット。
ちな、これの実装を愚直にやると「formのnameアトリビュート値が固定」になるんだけど。「いやまぁ固定でもいいじゃない」ってのと「可変にしたきゃ、デフォルト引数とかうまく使ってよ」とか、まぁ解決策はいくつか。
面倒なんで書かないけど、質問がきたら書くかも。


あと、細かいところでエラー処理。
まぁ例外投げる時は「例外のメッセージの中に、それこそjson文字列とかででも細かい情報突き返せばよくね?」とかアバウトにアバウトに。いやまぁModelインスタンスん中にエラー情報入れてgetってもいいだろうし。
戻り値でreturnであれば、別途「error_detail」とかってメソッド使って詳細吐き出せばよいと思うですます。


ってなわけで、これやると「Controllerでやることが減る」変わりに「Modelでやることが増える」んだよねぇ当たり前だよ動かしただけだもん。
ただ、Modelって「実のところ、なんなの?」って考えた時に。
もしModelが「ある程度のビジネス処理をつかさどるところ」なのであれば、一つの提案としては上述のようなコード「も」想定できるかなぁ、とか思うわけですます。


一方で「LaravelのController(の1メソッド)って、必ずしも"外部から呼ばれる"前提とは限らない」と思われるので(web.phpとかにルーティング書かなければ呼ばれないし)。
例えば「このControllerのこのメソッドは"このデータの塊を、入力受け取ってvalidateして保存する"用のメソッドなんだ!!」ってなるのであれば、それはそれで「そーゆー方向性と指針なんだなぁ」とも思うのです。「それって、ビジネスロジックって言わね?」とかって疑問もありますが、別にそれはそれで一貫性があれば。「Controllerにビジネスロジックを書いたら死ぬ」ってわけでもないだろうし。
いやまぁ結果的に「Controllerに書く内容が増えて、太りやすくなるよなぁ」とは思うのですが「それはそれでいいじゃん」であれば、それはそれでよいんじゃないかなぁ、とも思いますです。はい。


ってなわけで。
上述みたいなコードで「データに紐づく責務」をModel側に移すのって、世間的にはどーゆー反応なのかしらん? ってのが、おいちゃんの疑問。いや上述のような感じのコードって、見る見ないでいうと「ほとんど見かけない」ので。
「個人的見解と感想と好みと雑感と偏見」ざぶざぶでよいので、コメントなどいただけると、おいちゃんが喜んだりすると思われますので、ぜひ ノ

*1: 「ふとっちょのきょくちょう」(Fat Controller)、って、説明しなきゃわからないギャグを書くな