Laravel5.5でModelとControllerを分離する

FatControllerでModelが綺麗な方へ。。。
LaravelでControllerからModelに処理を移す解説をしている記事が少なかったので解説してみました。

``$this->``を復習したい方は以前の記事を先に見てください
https://qiita.com/satorunooshie/items/45fb85ea6c14cad2ac58
その他参考になる記事はこちらに貼っておきます
FatControllerについて
https://qiita.com/nunulk/items/6ed409345efb6ee4f660
FatModelについて
https://qiita.com/nunulk/items/dd27591b4a7a8a464a1a

#目次
[簡単な説明](#簡単な説明)
[リレーション](#リレーション)
[ThreadController](#threadcontroller)
[Thread.php](#threadphp)
[ThreadController解説](#threadcontroller解説)
[多次元配列部分の補足](#多次元配列部分の補足)
[RoomController解説](#roomcontroller解説)
[NG例](#ng例)
[RoomController](#roomcontroller)
[Room.php](#roomphp)
#簡単な説明
**モデルのイメージはコントローラのDB処理関連の共通部分を因数分解するようにまとめて再利用できるようにする感じです**
そうすることで``モデルで加工してコントローラではビューに渡すだけ``という構造が出来上がります
今回、コンストラクタインジェクションを用いていますが、
モデルのメソッドを呼ぶには**インスタンスを作成して**``シングルアロー->``で呼び出すことができます
>
よく言われることですが、オブジェクトはレシピでインスタンスは料理です
レシピはただの情報で実体がない感じを想像してください。
インスタンスを生成することで、やっと生誕するので、何か処理を施せるって感じ
すごい雑なので誰か添削してください笑
多分モデルに処理を移していくとわかった気になれます笑

参考記事:DIについて
https://qiita.com/harunbu/items/079ea728d2c9cf4f44d5
例えばこんな感じ

```php:ThreadController.php
public function index(Thread $thread)
{
    $threads = $thread->getAllThreads();
    return view('admin.thread.index', compact('threads'));
}
```
または

```php:ThreadController.php
public function index()
{
    $thread = new Thread;
    $threads = $thread->getAllThreads();
    //$threads = (new Thread)->getAllThreads();
    return view('admin.thread.index', compact('threads'));
}
```
こんな感じ？？
同じクラス内のメソッドを呼ぶには``$this->メソッド名``で行けます！
これは
あとは手を動かして試してみてください
#リレーション
Thread, Room, Replyそれぞれの関係はリレーションを見てください

```Thread.php
    public function room()
    {
        return $this->hasMany(Room::class);
    }

    public function message()
    {
        return $this->hasMany(Message::class);
    }
```

```
┏スレッド
┃  ┣ルーム
┃  ┃  ┣メッセージ
┃  ┃  ┃  ┣リプライ
┃  ┃  ┃  ┗リプライ
┃  ┃  ┗メッセージ
┃  ┗ルーム
┃     ┗メッセージ
┗スレッド
   ┗ルーム
```
みたいになってますね
全ての親要素が``スレッド``で、
その下に不特定多数の``ルーム``、
その下に不特定多数の``メッセージ``、
その下に不特定多数の``リプライ``があるって感じです


```Room.php
    public function thread()
    {
        return $this->belongsTo(Thread::class);
    }

    public function message()
    {
        return $this->belongsTo(Message::class);
    }
```



#ThreadController
それでは、コントローラを見ていきましょう。
記述が少なくメソッド名がわかりやすいため、一読でほとんど理解できるのではないでしょうか。

```Admin/ThreadController.php
<?php

namespace App\Http\Controllers\Admin\Thread;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\EditThreadRequest;
use App\Http\Requests\AddThreadRequest;
use Illuminate\Support\Facades\Auth;
use App\Models\Room;
use App\Models\Thread;

class ThreadController extends Controller
{
    //コンストラクタインジェクション
    public function __construct(Thread $thread, Room $room)
    {
        $this->thread = $thread;
        $this->room = $room;
    }

    public function index()
    {
        //ここでは多次元配列(multidimensional array)にスレッド内容を格納する
        $threadsMultiArr = $this->thread->setThreadInfo();
        return view('admin.thread.index', compact('threadsMultiArr'));
    }

    public function add()
    {
        return view('admin.thread.add');
    }

    //FormRequestを使ってバリデーション
    public function store(AddThreadRequest $request)
    {
        //スレッドを保存するためにstoreThreadメソッドを呼び出す
        $this->thread->storeThread($request, Auth::id());
        return redirect(route('admin.thread.index'))
            ->with('message', 'スレッドの追加が完了しました');
    }

    public function edit($id)
    {
        //編集するスレッドを取得してビューに渡す
        $thread = $this->thread->getThread($id);
        return view('admin.thread.edit', compact('thread'));
    }

    public function update(EditThreadRequest $request, $id)
    {
        //更新するスレッドを取ってくる
        $thread = $this->thread->getThread($id);
        //スレッドを更新するメソッドを呼び出す
        $this->thread->updateThread($thread, $request);
        return redirect(route('admin.thread.index'))
            ->with('message', 'スレッドの編集が完了しました。');
    }

    public function delete($id)
    {
        //まずは既にスレッドが非公開になっていないかを確認
        if (!$this->thread->threadExists($id)) {
            //既に非公開になっていた場合はエラー文言とともにリダイレクト
            return redirect(route('admin.thread.index'))
                ->with('error', 'このスレッドは既に非公開になっています。');
        }
        //非公開になっていない場合は非公開にするために論理削除する
        $this->thread->deleteThread($id);
        return redirect(route('admin.thread.index'))
            ->with('message', 'スレッドを非公開に設定しました。');
    }

    public function restore($id)
    {
        //スレッドが既に非公開になっているか確認
        if (!$this->thread->threadExists($id)) {
            //スレッドが既に非公開の場合は公開状態に戻すためにリストアしてリダイレクト
            $this->thread->restoreThread($id);
            return redirect(route('admin.thread.index'))
                ->with('message', 'スレッドを公開しました。');
        }
        //スレッドが公開されている場合はリストアできないのでエラー文言とともにリダイレクト
        return redirect(route('admin.thread.index'))
            ->with('error', 'このスレッドは既に公開されています。');
    }

    public function detail($id)
    {
        //詳細を表示するスレッドを取ってくる
        $thread = $this->thread->getThread($id);
        //スレッドに属している部屋全てを取ってくる
        $rooms = $this->room->getAllRoomsFromThread($thread->id);
        return view('admin.thread.detail', compact('thread', 'rooms'));
    }
}
```

#Thread.php
次にモデルを見ていきましょう
**モデルのイメージはコントローラの共通部分を因数分解するようにまとめて再利用できるようにする感じです**

```Thread.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Thread extends Model
{
    use SoftDeletes;
    protected $dates = ['deleted_at'];

    protected $hidden = [
        'id'
    ];
    //リレーション
    public function room()
    {
        return $this->hasMany(Room::class);
    }

    public function message()
    {
        return $this->hasMany(Message::class);
    }

    public function setThreadInfo(): Array
    {
        //同じモデル内のメソッドを呼び出す
        $threads = $this->getAllThreads();
        //スレッドを多次元配列に格納する
        foreach ($threads as $thread) {
            $threadsMultiArr[$thread->name]['id'] = $thread->id;
            $threadsMultiArr[$thread->name]['cntReply'] = $thread->message()->count();
            //リレーションを使ってメッセージを取ってくる
            $messages = $thread->message()->get();
            $threadsMultiArr[$thread->name]['cntMessage'] = 0;
            //メッセージが複数ある場合はカウントを更新する
            foreach ($messages as $message) {
                $threadsMultiArr[$thread->name]['cntMessage'] += $message->reply()->count();
            }
            $threadsMultiArr[$thread->name]['deleted_at'] = (!is_null($thread->deleted_at)) ? true : false;
        }
        return $threadsMultiArr;
    }

    public function setThreadsForPullDown(): Array
    {
        //削除済みスレッドを含めて取得する
        $threads_tmp = $this->getAllThreads();
        //スレッドの初期化
        $threads = [];
        //セレクトボックスの`value`に`id`をセットする
        foreach ($threads_tmp as $thread) {
            $threads[$thread->id] = $thread->name;
        }
        return $threads;
    }

    public function getAllThreads()
    {
        //スレッドを削除済みも含めて全て取得する
        $threads = $this->withTrashed()->whereNotNull('id')->get();
        return $threads;
    }

    public function getThread($id)
    {
        //idからスレッドを取得する
        $thread = $this->withTrashed()->findOrFail($id);
        return $thread;
    }

    public function storeThread($request, $auth): bool
    {
        $this->name = $request->name;
        $this->user_id = $auth;
        $this->save();
        return true;
    }

    public function updateThread($thread, $request): bool
    {
        $thread->name = $request->name;
        $thread->save();
        return true;
    }

    public function threadExists($id): bool
    {
        //スレッドが削除されていないかを確認する
        $threadExists = $this->where('id', $id)->exists();
        return $threadExists;
    }

    public function deleteThread($id): bool
    {
        $this->where('id', $id)->delete();
        return true;
    }

    public function restoreThread($id): bool
    {
        $this->where('id', $id)->restore();
        return true;
    }
}
```
#ThreadController解説
ここでは一部分を抽出して解説して行きます
他は全部同じ要領で行けばいけるので！
以下は一部抜粋です

```threadController.php
    public function update(EditThreadRequest $request, $id)
    {
        //更新するスレッドを取ってくる
        $thread = $this->thread->getThread($id);
        //スレッドを更新するメソッドを呼び出す
        $this->thread->updateThread($thread, $request);
        return redirect(route('admin.thread.index'))
            ->with('message', 'スレッドの編集が完了しました。');
    }
```
ここではスレッドの更新処理を行っている
コントローラーだけで書くなら

```example.php
    public function update(EditThreadRequest $request, $id)
    {
        $thread = $this->thread->findOrFail($id);
        try {
            $thread->fill($request->all())->save();
        } catch (ModelNotFoundException $e) {
            return redirect(route('admin.thread.index'))
                ->with('error', 'message.any_thread');
        }
        return redirect(route('admin.thread.index'))
            ->with('message', 'スレッドの編集が完了しました');
    }
```
とかいう感じになっていると思うのでかなり読みやすくなったと思います
もう一つくらい解説しておきます


```threadController.php
    public function delete($id)
    {
        if (!$this->thread->threadExists($id)) {
            return redirect(route('admin.thread.index'))
                ->with('error', 'このスレッドは既に非公開になっています。');
        }
        $this->thread->deleteThread($id);
        return redirect(route('admin.thread.index'))
            ->with('message', 'スレッドを非公開に設定しました。');
    }
```
これはただ論理削除しているだけ
コントローラーだけで書くなら

```example.php
    $thread = $this->thread->findOrFail($id);
    if (!$thread) return redirect()->back()->with('error', 'message.any_thread');
    if (!$thread->delete()) return redirect(route('admin.thread.index'))->with('error', 'スレッドを非公開にできませんでした');
    return redirect(route('admin.thread.index'))
        ->with('message', 'スレッドを非公開にしました');
```

##多次元配列部分の補足
多次元配列部分をどうやって使っているかイメージしづらいと思うので下にビューを貼っておきます

```php:
@foreach ($threadsMultiArr as $threadName => $threadInfo)
    <tr>
        <td>
            <p>{{ $threadInfo['id'] }}</p>
        </td>
        <td>
            <a href={{ route('admin.thread.detail', $threadInfo['id']) }}>{{ $threadName }}</a>
        </td>
        <td>
            <p>{{ $threadInfo['cntReply'] }}</p>
        </td>
        <td>
            <p>{{ $threadInfo['cntMessage'] }}</p>
        </td>
        <td>
            @if ($threadInfo['deleted_at'])
                <p>非公開</p>
            @else
                <p>公開</p>
            @endif
        </td>
        <td>
            {!! Form::open(['method' => 'get', 'url' => route('admin.thread.edit', $threadInfo['id'])]) !!}
                <button class="back">編集する</button>
            {{ Form::close() }}
        </td>
        <td>
        @if ($threadInfo['deleted_at'] == false)
            {!! Form::open(['method' => 'post', 'url' => route('admin.thread.delete', $threadInfo['id']), 'onsubmit' => "return confirm('本当に非公開にしますか')"]) !!}
                <button class="back">非公開にする</button>
            {{ Form::close() }}
        @else
            {!! Form::open(['method' => 'post', 'url' => route('admin.thread.restore', $threadInfo['id'])]) !!}
                <button class="back">公開する</button>
            {{ Form::close() }}
        @endif
        </td>
    </tr>
@endforeach

```
#RoomController解説
今の例だと、あんまかわんねえじゃんって声が聞こえて来そうですね笑
次のコードで解説したいのはここだけです

```RoomController.php
    public function allRooms()
    {
        //のちのNG例
        //$roomsAsc = $this->room->getAllRooms();
        //$roomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId($roomsAsc);
        $roomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId();
        return view('room.allRooms', compact('roomsMultiArr'));
    }

    public function myRooms()
    {
        $user = $this->user->findOrFail(Auth::id());
        //のちのNG例
        //$myRoomsAsc = $this->room->getMyRooms(Auth::id());
        //$myRoomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId($myRoomsAsc);
        $myRoomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId(Auth::id());
        return view('room.myRooms', compact('user', 'myRoomsMultiArr'));
    }
```
ここで呼び出しているメソッドは以下のとおりです


```Room.php
    public function getRooms(?int $auth = null)
    {
        //引数で条件分岐するとメソッドが一つになる
        if ($auth == null) {
            $roomsAsc = $this->orderBy('thread_id', 'asc')->get();
        } else {
            $roomsAsc = $this->where('user_id', $auth)->orderBy('thread_id', 'asc')->get();
        }
        return $roomsAsc;
    }

    public function convertToMultidimentionalArrayByThreadId(?int $auth = null): Array
    {
        //ここで同じメソッドのモデルを呼び出す
        $roomsAsc = $this->getRooms($auth);
        foreach ($roomsAsc as $room) {
            $threadInfo = $room->thread()->first();
            //keyをthreadの名前にする
            if (isset($roomsMultiArr[$threadInfo->name])) {
                array_push($roomsMultiArr[$threadInfo->name], $room->name);
            } else {
                $roomsMultiArr[$threadInfo->name] = array($room->name);
            }
        }
        return $roomsMultiArr;
    }
```
`allRooms()`と`myRooms()`で同じメソッド``convertToMultidimentionalArrayByThreadId(?int $auth = null)``を呼んでいるが
引数の有無によって処理の仕方を分けている
これを愚直にModelに移そうとするとほぼ同じメソッドをModelに記述することになってしまい
このModelを使ってコントローラーを記述すると下のような冗長な記述になってしまう
#NG例

```php:NG_Controller
    public function allRooms()
    {
        $roomsAsc = $this->room->getAllRooms();
        $roomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId($roomsAsc);
        return view('room.allRooms', compact('roomsMultiArr'));
    }

    public function myRooms()
    {
        $user = $this->user->findOrFail(Auth::id());
        $myRoomsAsc = $this->room->getMyRooms(Auth::id());
        $myRoomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId($myRoomsAsc);
        return view('room.myRooms', compact('user', 'myRoomsMultiArr'));
    }
```

```php:NG_Model
    public function getRooms()
    {
        $roomsAsc = $this->orderBy('thread_id', 'asc')->get();
        return $roomsAsc;
    }
    public function getMyRooms($auth)
    {
        $myRoomsAsc = $this->where('user_id', $auth)->orderBy('thread_id', 'asc')->get();
        return $myRoomsAsc;
    }
```
#RoomController
一応解説していないところも乗っけておきます！

```RoomController.php
<?php

namespace App\Http\Controllers\Room;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\ContentRequest;
use Illuminate\Support\Facades\Auth;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\File;
use App\Models\Message;
use App\Models\Reply;
use App\Models\Room;
use App\Models\User;
use App\Models\Favorite;

class RoomController extends Controller
{
    public function __construct(Message $message, Reply $reply, Room $room, User $user, Favorite $favorite)
    {
        $this->message = $message;
        $this->reply = $reply;
        $this->room = $room;
        $this->user = $user;
        $this->favorite = $favorite;
    }

    public function show($id)
    {
        $user = $this->user->findOrFail(Auth::id());
        $room = $this->room->getRoom($id);
        $isFavoriteRoom = $this->favorite->isUserIdAndRoomIdMatch($user->id,　$room->id);
        if (!$room) return view('home');
        $messages = $this->message->getMessages($id);
        $reply = $this->reply->convertToMessageReply();
        $favs = $this->reply->convertToMessageFavorites($id);
        return view('room.show', compact('room', 'messages', 'reply', 'favs', 'user', 'isFavoriteRoom'));
    }

    public function createForm($id)
    {
        $room = $this->room->getRoom($id);
        if (!$room) return view('home');
        return view('room.create', compact('room'));
    }

    public function store(ContentRequest $request)
    {
        $room = $this->room->getRoom($request->room_id);
        if (!$room) {
            return view('room.create', compact('room'));
        }

        if ($request->image) {

            if (!File::exists(public_path() . '/storage/' . $room->name)) {
                File::makeDirectory(public_path() . '/storage/' . $room->name, 0775, true);
            }

            $img = Image::make($request->image);
            $img_path = uniqid() . '_' . Auth::user()->name . '.jpg';
            $img->save(public_path() . '/storage/' . $room->name . '/' . $img_path);
            $image = $room->name . '/' . $img_path;
            $this->message->image = $image;
        }
        $this->message->user_id = Auth::id();
        $this->message->thread_id = $room->thread_id;
        $this->message->room_id = $room->id;
        $this->message->content = $request->content;
        $this->message->save();
        return redirect()->route('room.show', [$request->room_id])
            ->with('message', '追加しました');
    }

    public function edit($id)
    {
        $message = $this->message->getMessage($id);
        return view('room.edit', compact('message'));
    }

    public function update(ContentRequest $request, $id)
    {
        $message = $this->message->getMessage($id);
        $result = $this->message->updateMessage($message, $request);
        return redirect()->route('room.show', [$message->room_id])
            ->with($result ? 'message' : 'error', $result ? '編集しました' : '編集に失敗しました');
    }

    public function allRooms()
    {
        $roomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId();
        return view('room.allRooms', compact('roomsMultiArr'));
    }

    public function myRooms()
    {
        $user = $this->user->findOrFail(Auth::id());
        $myRoomsMultiArr = $this->room->convertToMultidimentionalArrayByThreadId(Auth::id());
        return view('room.myRooms', compact('user', 'myRoomsMultiArr'));
    }
}
```
次は管理者コントローラ

```Admin/RoomController.php
<?php

namespace App\Http\Controllers\Admin\Room;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\AddRoomRequest;
use App\Models\Thread;
use App\Models\Room;
use App\Models\Plan;

class RoomController extends Controller
{
    public function __construct(Thread $thread, Room $room, Plan $plan)
    {
        $this->thread = $thread;
        $this->room = $room;
        $this->plan = $plan;
    }

    public function add($thread_id = null)
    {
        $threads = $this->thread->setThreadsForPullDown();
        $plans = $this->plan->setPlansForPullDown();
        return view('admin.room.add', compact('threads', 'plans', 'thread_id'));
    }

    public function store(AddRoomRequest $request)
    {
        if ($this->thread->threadExists($request->thread_id) && $this->plan->planExists($request->plan_id)) {
            $this->room->storeRoom($request, Auth::id());
            return redirect(route('admin.thread.detail', $request->thread_id))->with('message', 'ROOMの追加が完了しました');
        }
        return redirect()->back()->with('error', '不正な値です');

    }

    public function delete($id)
    {
        if (!$this->room->roomExists($id)) {
            return redirect()->back()->with('error', 'このルームは既に非公開になっています。');
        }
        $this->room->deleteRoom($id);
        return redirect()->back()->with('message', 'ルームを非公開にしました。');
    }

    public function restore($id)
    {
        if (!$this->room->roomExists($id)) {
            $this->room->restoreRoom($id);
            return redirect()->back()->with('message', 'ルームを公開しました。');
        }
        return redirect()->back()->with('error', 'このルームは既に公開されています。');
    }
}
```


#Room.php

```Room.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Room extends Model
{
    use SoftDeletes;
    protected $dates = ['deleted_at'];

    protected $hidden = [
        'id'
    ];

    public function thread()
    {
        return $this->belongsTo(Thread::class);
    }

    public function message()
    {
        return $this->belongsTo(Message::class);
    }

    public function getRoom($room_id)
    {
        return $this->find($room_id);
    }

    public function getRooms($auth = null)
    {
        //引数で条件分岐するとメソッドが一つになる
        if ($auth == null) {
            $roomsAsc = $this->orderBy('thread_id', 'asc')->get();
        } else {
            $roomsAsc = $this->where('user_id', $auth)->orderBy('thread_id', 'asc')->get();
        }
        return $roomsAsc;
    }

    public function getAllRoomsFromThread($thread_id)
    {
        //Allには削除済みも含まれる
        $rooms = $this->where('thread_id', $thread_id)->withTrashed()->get();
        return $rooms;
    }

    public function convertToMultidimentionalArrayByThreadId(?int $auth = null): Array
    {
        $roomsAsc = $this->getRooms($auth);
        foreach ($roomsAsc as $room) {
            $threadInfo = $room->thread()->first();
            //keyをthreadの名前にする
            if (isset($roomsMultiArr[$threadInfo->name])) {
                array_push($roomsMultiArr[$threadInfo->name], $room->name);
            } else {
                $roomsMultiArr[$threadInfo->name] = array($room->name);
            }
        }
        return $roomsMultiArr;
    }

    public function storeRoom($request, $auth)
    {
        $this->name = $request->name;
        $this->thread_id = $request->thread_id;
        $this->plan_id = $request->plan_id;
        $this->user_id = $auth;
        $this->save();
        return true;
    }

    public function roomExists($id)
    {
        $roomExists = $this->where('id', $id)->exists();
        return $roomExists;
    }

    public function deleteRoom($id)
    {
        $this->where('id', $id)->delete();
        return true;
    }

    public function restoreRoom($id)
    {
        $this->where('id', $id)->restore();
        return true;
    }
}
```