مدل چاق و کنترلر لاغر در لاراول

مدل ها و کنترلر ها از اصلی ترین ارکان در لاراول و خیلی از فریم ورک های MVC هستند که کارایی متفاوتی دارند و در عملیات های زیادی استفاده میشه. کنترلر ها پیشفرض توی پوشه App/Http/Controllers و مدل ها توی پوشه App قرار گرفتند. مدل ها بیشتر برای ارتباط با دیتابیس به وسیله Eloquent ORM استفاده میشوند.


شما به عنوان یک برنامه نویس باید بدونید چجوری تعادل بین استفاده از مدل و کنترلر هاتون برقرار سازید و اینکه کدوم یک باید وظیفه برعهده بگیره.


مفهوم مدل چاق و کنترلر لاغر

اساس این کار یعنی ما بیشتر کار هامون(کد هامون) رو توی مدل ها و کارای کمتری در کنترلر انجام میدیم.

توی مدل ها کدای اصلی و منطق برنامه رو تعریف میکنیم. در حالی که در کنترلر بیشتر دیتای ارسالی رو ولیدیت و یا تایید میکنیم و به مدل میفرستیم تا کار اصلی رو انجام بده.این فرآیند ساختاربندی یک مفهوم بسیار ابتدایی از MVC و همچنین فاکتور تمایز از برنامه‌نویسی پیچیده معمولی است که گاهی اوقات به اشتباه آن را نادیده می‌گیریم.


چرا کنترلرهای چاق برای هندل کردن کد بد هستند؟؟؟


از کنترلرها همیشه باید کوتاه و مختصر تعریف شوند، و تنها باید برای دریافت درخواست(request) و پاسخ به آن مورد استفاده قرار گیرد. هر چیز دیگری بیشتر باید در مدل‌ها برنامه‌ریزی شود، که در واقع برای عملیات عملکردی اصلی ساخته شده‌است.

قرار دادن منطق‌های کاربردی در کنترلرها می‌تواند به دلایل بسیاری بد باشد، چون نه تنها ساختار کد را طولانی می‌سازد، بلکه گاهی اوقات آن را پیچیده می‌کند. قرار دادن کد در کنترلرها به این دلیل توصیه نمی‌شود که اگر یک کارکرد در یک جای دیگر در مسیر مورد نیاز باشد، آنگاه کل کد را از آن‌ها بیرون بکشید و این کار قابلیت استفاده مجدد آن را در برنامه دارد.

اگه کنترلر ها رو مثل میز کا در نظر بگیریم مانند عکس های زیر میشن:

میز کار تمییز(کنترلر لاغر)
میز کار تمییز(کنترلر لاغر)
میز کار درهم(کنترلر چاق)
میز کار درهم(کنترلر چاق)



درسته که لاراول بر اساس MVC ساخته شده ولی خیلی وقتا اینو فراموش میکنیم و میایم تموم کدای موجود رو در در مدل و کنترلر(و یا حتی روت) مربوطه مینویسم در حالی که خاصیت اثربری شی گرایی رو کلا یادمون میره و انگار کلا نمی دونیم از هر مدل هم میشه ساب مدل داشت. برای مثال مدل User میتونه Custumer داشته باشه :)



حالا یک مثال ببینیم از کدای درهم برهمی که با laravel برای ساختن کامنت های یک وبلاگ نوشته شده رو ببینید:


<?php 
namespace App;  

use App\Comment; 
use App\CommentVote; 
use App\CommentSpam; 
use App\User; 
use Auth;   

class CommentModel 
{ 
}

?>

توی این مدل قراره کل منطق برنامه نوشته بشه. در حال حاضر آن هیچ تابع وجود ندارد، اما مرجع تمام مدل هاست که برای این مدل مورد نیاز است. ابتدا یک تابع به نام getallcomments اضافه کنیم که pageId$ را به عنوان پارامتر به آن پاس داده میشه. تابع تمام نظرات مربوط به صفحه داده شده را دریافت می کند:

public function getAllComments($pageId)
   {
       $comments = Comment::where('page_id',$pageId)->get();

       $commentsData = [];
      
       foreach ($comments as $key) {
           $user = User::find($key->users_id);
           $name = $user->name;
           $replies = $this->replies($key->id);
           $photo = $user->first()->photo_url;
           // dd($photo->photo_url);
           $reply = 0;
           $vote = 0;
           $voteStatus = 0;
           $spam = 0;
           if(Auth::user()){
               $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
               $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
              
               if($voteByUser){
                   $vote = 1;
                   $voteStatus = $voteByUser->vote;
               }

               if($spamComment){
                   $spam = 1;
               }
           }
          
           if(sizeof($replies) > 0){
               $reply = 1;
           }

           if(!$spam){
               array_push($commentsData,[
                   "name" => $name,
                   "photo_url" => (string)$photo,
                   "commentid" => $key->id,
                   "comment" => $key->comment,
                   "votes" => $key->votes,
                   "reply" => $reply,
                   "votedByUser" =>$vote,
                   "vote" =>$voteStatus,
                   "spam" => $spam,
                   "replies" => $replies,
                   "date" => $key->created_at->toDateTimeString()
               ]);
           }   
          
       }
       $collection = collect($commentsData);
       return $collection->sortBy('votes');
   }

یک تابع دیگر با نام پاسخ ها ایجاد می کنیم که commentId$ را به عنوان پارامتر می گیرد. تابع کمو بیش مثل متد بالایی برنامه نویسی شده.


protected function replies($commentId) {       
    $comments = Comment::where('reply_id',$commentId)->get();   
        $replies = [];                
        foreach ($comments as $key) {            
            $user = User::find($key->users_id);            
            $name = $user->name;            
            $photo = $user->first()->photo_url;            
            $vote = 0;            
            $voteStatus = 0;            
            $spam = 0;                          
            if(Auth::user()){                
                $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id', Auth::user()->id)->first();                
                $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id', Auth::user()->id)->first();                 
                if($voteByUser){                    
                    $vote = 1;                    
                    $voteStatus = $voteByUser->vote;               
                 }                
                 if($spamComment){                    
                     $spam = 1;               
                 }            
             }            
             if(!$spam){                               
                 array_push($replies,[                    
                     "name" => $name,                    
                     "photo_url" => $photo,                    
                     "commentid" => $key->id,                    
                     "comment" => $key->comment,                    
                     "votes" => $key->votes,                   
                      "votedByUser" => $vote,                    
                      "vote" => $voteStatus,                   
                      "spam" => $spam,                   
                      "date" => $key->created_at->toDateTimeString()                
                    ]);            
                }                          
            }               
            $collection = collect($replies);        
            return $collection->sortBy('votes');    
        }

اکنون یک تابع createComment ایجاد کنید که array$ را به عنوان پارامتر در آن قرار می دهد:

public function createComment($arary) {        
    $comment = Comment::create($array);                
    if($comment)            
        return [ "status" => "true","commentId" => $comment->id ];        
    else return [ "status" => "false" ];       
}

به همین ترتیب، اکنون تمام تابع برای نظر در CommentModel را ایجاد می کنم تا تمام توابع در یک مدل انباشته شوند.


<?php
namespace App;

use App\Comment;
use App\CommentSpam;
use App\CommentVote;
use App\User;
use Auth;

class CommentModel
{
    public function getAllComments($pageId)
    {
        $comments = Comment::where('page_id', $pageId)->get();

        $commentsData = [];

        foreach ($comments as $key) {
            $user = User::find($key->users_id);
            $name = $user->name;
            $replies = $this->replies($key->id);
            $photo = $user->first()->photo_url;
            // dd($photo->photo_url);
            $reply = 0;
            $vote = 0;
            $voteStatus = 0;
            $spam = 0;
            if (Auth::user()) {
                $voteByUser = CommentVote::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();
                $spamComment = CommentSpam::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

                if ($voteByUser) {
                    $vote = 1;
                    $voteStatus = $voteByUser->vote;
                }

                if ($spamComment) {
                    $spam = 1;
                }
            }

            if (sizeof($replies) > 0) {
                $reply = 1;
            }

            if (!$spam) {
                array_push($commentsData, [
                    "name" => $name,
                    "photo_url" => (string) $photo,
                    "commentid" => $key->id,
                    "comment" => $key->comment,
                    "votes" => $key->votes,
                    "reply" => $reply,
                    "votedByUser" => $vote,
                    "vote" => $voteStatus,
                    "spam" => $spam,
                    "replies" => $replies,
                    "date" => $key->created_at->toDateTimeString(),
                ]);
            }

        }
        $collection = collect($commentsData);
        return $collection->sortBy('votes');
    }

    protected function replies($commentId)
    {
        $comments = Comment::where('reply_id', $commentId)->get();
        $replies = [];

        foreach ($comments as $key) {
            $user = User::find($key->users_id);
            $name = $user->name;
            $photo = $user->first()->photo_url;

            $vote = 0;
            $voteStatus = 0;
            $spam = 0;

            if (Auth::user()) {
                $voteByUser = CommentVote::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();
                $spamComment = CommentSpam::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

                if ($voteByUser) {
                    $vote = 1;
                    $voteStatus = $voteByUser->vote;
                }

                if ($spamComment) {
                    $spam = 1;
                }
            }
            if (!$spam) {

                array_push($replies, [
                    "name" => $name,
                    "photo_url" => $photo,
                    "commentid" => $key->id,
                    "comment" => $key->comment,
                    "votes" => $key->votes,
                    "votedByUser" => $vote,
                    "vote" => $voteStatus,
                    "spam" => $spam,
                    "date" => $key->created_at->toDateTimeString(),
                ]);
            }

        }

        $collection = collect($replies);
        return $collection->sortBy('votes');
    }

    public function createComment($arary)
    {
        $comment = Comment::create($array);

        if ($comment) {
            return ["status" => "true", "commentId" => $comment->id];
        } else {
            return ["status" => "false"];
        }

    }

    public function voteComment($commentId, $array)
    {
        $comments = Comment::find($commentId);
        $data = [
            "comment_id" => $commentId,
            'vote' => $array->vote,
            'user_id' => $array->users_id,
        ];

        if ($array->vote == "up") {
            $comment = $comments->first();
            $vote = $comment->votes;
            $vote++;
            $comments->votes = $vote;
            $comments->save();
        }
        if ($array->vote == "down") {
            $comment = $comments->first();
            $vote = $comment->votes;
            $vote--;
            $comments->votes = $vote;
            $comments->save();
        }
        if (CommentVote::create($data)) {
            return true;
        }
    }
    public function spamComment($commentId, $array)
    {
        $comments = Comment::find($commentId);
        $comment = $comments->first();
        $spam = $comment->spam;
        $spam++;
        $comments->spam = $spam;
        $comments->save();
        $data = [
            "comment_id" => $commentId,
            'user_id' => $array->users_id,
        ];
        if (CommentSpam::create($data)) {
            return true;
        }
    }
}
?>

در حال حاضر ما تمام روش های مورد نیاز ما در CommentModel داریم. بنابراین حالا بیایید CommentController را تمیز کنیم که در حال حاضر در ساختار کد کمی پیچیده و طولانی است. همانطور که در حال حاضر CommentController به نظر می رسد این است:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Comment;
use App\CommentVote;
use App\CommentSpam;
use App\User;
use Auth;

class CommentController extends Controller
{

    /**
     * Get Comments for pageId
     *
     * @return Comments
     */
    public function index($pageId)
    {
        //
        $comments = Comment::where('page_id',$pageId)->get();

        $commentsData = [];


        foreach ($comments as $key) {
            $user = User::find($key->users_id);
            $name = $user->name;
            $replies = $this->replies($key->id);
            $photo = $user->first()->photo_url;
            // dd($photo->photo_url);
            $reply = 0;
            $vote = 0;
            $voteStatus = 0;
            $spam = 0;
            if(Auth::user()){
                $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
                $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

                if($voteByUser){
                    $vote = 1;
                    $voteStatus = $voteByUser->vote;
                }

                if($spamComment){
                    $spam = 1;
                }
            }

            if(sizeof($replies) > 0){
                $reply = 1;
            }

            if(!$spam){
                array_push($commentsData,[
                    "name" => $name,
                    "photo_url" => (string)$photo,
                    "commentid" => $key->id,
                    "comment" => $key->comment,
                    "votes" => $key->votes,
                    "reply" => $reply,
                    "votedByUser" =>$vote,
                    "vote" =>$voteStatus,
                    "spam" => $spam,
                    "replies" => $replies,
                    "date" => $key->created_at->toDateTimeString()
                ]);
            }

        }
        $collection = collect($commentsData);
        return $collection->sortBy('votes');
    }

    protected function replies($commentId)
    {
        $comments = Comment::where('reply_id',$commentId)->get();
        $replies = [];


        foreach ($comments as $key) {
            $user = User::find($key->users_id);
            $name = $user->name;
            $photo = $user->first()->photo_url;

            $vote = 0;
            $voteStatus = 0;
            $spam = 0;

            if(Auth::user()){
                $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
                $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

                if($voteByUser){
                    $vote = 1;
                    $voteStatus = $voteByUser->vote;
                }

                if($spamComment){
                    $spam = 1;
                }
            }
            if(!$spam){

                array_push($replies,[
                    "name" => $name,
                    "photo_url" => $photo,
                    "commentid" => $key->id,
                    "comment" => $key->comment,
                    "votes" => $key->votes,
                    "votedByUser" => $vote,
                    "vote" => $voteStatus,
                    "spam" => $spam,
                    "date" => $key->created_at->toDateTimeString()
                ]);
            }


        }

        $collection = collect($replies);
        return $collection->sortBy('votes');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'comment' => 'required',
            'reply_id' => 'filled',
            'page_id' => 'filled',
            'users_id' => 'required',
        ]);
        $comment = Comment::create($request->all());
        // dd($comment);
        if($comment)
            return [ "status" => "true","commentId" => $comment->id ];
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  $commentId
     * @param  $type
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $commentId,$type)
    {
        if($type == "vote"){

            $this->validate($request, [
                'vote' => 'required',
                'users_id' => 'required',
            ]);

            $comments = Comment::find($commentId);
            $data = [
                "comment_id" => $commentId,
                'vote' => $request->vote,
                'user_id' => $request->users_id,
            ];

            if($request->vote == "up"){
                $comment = $comments->first();
                $vote = $comment->votes;
                $vote++;
                $comments->votes = $vote;
                $comments->save();
            }

            if($request->vote == "down"){
                $comment = $comments->first();
                $vote = $comment->votes;
                $vote--;
                $comments->votes = $vote;
                $comments->save();
            }

            if(CommentVote::create($data))
                return "true";
        }

        if($type == "spam"){

            $this->validate($request, [
                'users_id' => 'required',
            ]);

            $comments = Comment::find($commentId);

            $comment = $comments->first();
            $spam = $comment->spam;
            $spam++;
            $comments->spam = $spam;
            $comments->save();

            $data = [
                "comment_id" => $commentId,
                'user_id' => $request->users_id,
            ];

            if(CommentSpam::create($data))
                return "true";
        }
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}
?>

پس از تمیزکردن کنترلر، کد برای درک بسیار ساده و آسان خواهد بود:
<?php


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Comment;
use App\CommentVote;
use App\CommentSpam;
use App\User;
use Auth;

class CommentController extends Controller
{

    /**
     * Get Comments for pageId
     *
     * @return Comments
     */
    public function index($pageId)
    {
        //
        $comments = Comment::where('page_id',$pageId)->get();

        $commentsData = [];


        foreach ($comments as $key) {
            $user = User::find($key->users_id);
            $name = $user->name;
            $replies = $this->replies($key->id);
            $photo = $user->first()->photo_url;
            // dd($photo->photo_url);
            $reply = 0;
            $vote = 0;
            $voteStatus = 0;
            $spam = 0;
            if(Auth::user()){
                $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
                $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

                if($voteByUser){
                    $vote = 1;
                    $voteStatus = $voteByUser->vote;
                }

                if($spamComment){
                    $spam = 1;
                }
            }

            if(sizeof($replies) > 0){
                $reply = 1;
            }

            if(!$spam){
                array_push($commentsData,[
                    "name" => $name,
                    "photo_url" => (string)$photo,
                    "commentid" => $key->id,
                    "comment" => $key->comment,
                    "votes" => $key->votes,
                    "reply" => $reply,
                    "votedByUser" =>$vote,
                    "vote" =>$voteStatus,
                    "spam" => $spam,
                    "replies" => $replies,
                    "date" => $key->created_at->toDateTimeString()
                ]);
            }

        }
        $collection = collect($commentsData);
        return $collection->sortBy('votes');
    }

    protected function replies($commentId)
    {
        $comments = Comment::where('reply_id',$commentId)->get();
        $replies = [];


        foreach ($comments as $key) {
            $user = User::find($key->users_id);
            $name = $user->name;
            $photo = $user->first()->photo_url;

            $vote = 0;
            $voteStatus = 0;
            $spam = 0;

            if(Auth::user()){
                $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
                $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

                if($voteByUser){
                    $vote = 1;
                    $voteStatus = $voteByUser->vote;
                }

                if($spamComment){
                    $spam = 1;
                }
            }
            if(!$spam){

                array_push($replies,[
                    "name" => $name,
                    "photo_url" => $photo,<?php

namespace App\Http\Controllers;

use App\CommentModel;
use Illuminate\Http\Request;

class CommentController extends Controller
{

    private $commentModel = null;
    private function __construct()
    {
        $this->commentModel = new CommentModel();
    }

    /**
     * Get Comments for pageId
     *
     * @return Comments
     */
    public function index($pageId)
    {
        return $this->commentModel->getAllComments($pageId);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'comment' => 'required',
            'reply_id' => 'filled',
            'page_id' => 'filled',
            'users_id' => 'required',
        ]);
        return $this->commentModel->createComment($request->all());
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  $commentId
     * @param  $type
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $commentId, $type)
    {
        if ($type == "vote") {

            $this->validate($request, [
                'vote' => 'required',
                'users_id' => 'required',
            ]);

            return $this->commentModel->voteComment($commentId, $request->all());
        }

        if ($type == "spam") {

            $this->validate($request, [
                'users_id' => 'required',
            ]);

            return $this->commentModel->spamComment($commentId, $request->all());
        }
    }

}
?>

جمع بندی

اینجوری کد بنظرتون خیلی برای درک بهتر اسان تر نیست؟ معمولا کنترلر لاغر و مدل چاق اینجوری هستند. ما تمام منطق ما مربوط به سیستم Comment را که در CommentModel ما برنامه نویسی شده است، کنترل می کنیم و اکنون فقط برای انتقال اطلاعات از کاربر به مدل ما و بازگشت پاسخ که از مدل ما استفاده می شود.

البته این روش های بهتری هم وجود دارند که بعدا دربارشون مینویسم.

منبع