チャットアプリで、1グループのメッセージ数上限を50件にする(Laravel)


はじめに

初心者による初心者向けの記事です。
陥りやすい様々な苦手分野も交えて書いてみました。
「collectionと配列の違い」「モデルインスタンスってなんやねん!」「クエリビルダとかメソッドとかわからん!」「ネットでcount()関数について調べてから使ったのに、エラーになる!」など。

このように実装しようと思った背景

実用的なチャットアプリを作りたいため、今回の実装をしました。
テーブル関係はgroups hasmany messagesの1対多です。
私はAjax通信のリアルタイムチャットアプリを作成しています。LINEのグループチャット的なものです。
トークルームのAjaxは、では5秒おき(setTimeout("get_messageC()",5000);)にそのグループの全メッセージを取得し、表示してあるメッセージを全部消して(.remove();)全部書き足す(.append(html);)という実装をしています。
しかしこれでは、メッセージが1000,2000と増えるごとに動作が重くなってしまいます。よって、1グループメッセージは50件までしか保存されないような設計にしました。そうすれば最大でも50件しか書き換えと表示が行われません。

やること

★メッセージ送信アクション時に、そのgroup_idと関連づいたmessageの数を数える(ここでcollectionと配列の違いを理解しとく必要がある)→
★50件以上あるなら、上位50件以外のメッセージは削除する(ここでモデルインスタンスなのかDBインスタンスなのか把握しておく必要がある)

下図messageテーブルです。ターミナルにてmysqlで出しました。見づらいですが、messagesテーブルのカラムにgroup_idがあります。user_idはグループ作成者です。いざという時の拡張性を考えて入れときました。

collectionと配列の違い

collectionはLaravelが提供する高機能な配列です。一方で配列はみなさんがよく見かけるphpの「the・普通の配列」です。
データをインスタンス化した時はcollectionであることが多いです。例えば下記のようにモデルをインスタンス化した時。
例:$apple = new Apple;
下記のように自分で配列を作った時は、紛れもなくphpの配列です。
例:$apple = array('iphone','macbook' ,'ipad');

違いを理解すべき瞬間

厄介なのは、「配列の要素数を数えるcount関数」1つとっても、collectionと配列で文法が異なることです。先ほどの$appleを例に取りましょう。
collectionの場合$count = $apple->count();
配列の場合$count = count($apple);
ちなみに配列関数count()をcollectionに適用すると以下のようなエラーが出ます。
ErrorException (E_WARNING)
count(): Parameter must be an array or an object that implements Countable

「count()関数は引数は配列かオブジェクトじゃないとダメですよ〜」ということですね。collectionを配列とみなしていないようです。
ちなみに僕もなんのインスタンスがcollectionになるのか、というのは把握していません。エラー文見てその都度判断してます。

書いたコード

先にクラスをuseしちゃいましょう。自分のコントローラーにあるものをコピペします。

今回使用するクラス

注目すべき箇所は「★」をつけてます。今回は★をuseしておけば問題ないと思います。

GroupController.php
<?php

namespace App\Http\Controllers\User;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB; //★
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Auth;
use Illuminate\Support\Facades\Hash;
use App\User;
use App\Follow;
use Abraham\TwitterOAuth\TwitterOAuth;
use App\Group;
use App\Message; //★
use Illuminate\Support\Facades\Log;
use Storage; 
GroupController.php@send
///まず現在チャットしているgroupのメッセージ数を全て取得します。
    $count = $message->where('group_id',$_POST['group_id'])->count();
///その数が50を超えているなら、
    if ($count > 50) {
///現在チャットしてるグループがhasするmessageのidを取得するためにやはりまずメッセージを取得します
      $deleteid = DB::table('messages')
      ->where('group_id', $_POST['group_id'])
///それの並び順を古い順から新しい順に変えます。
      ->orderBy('id','desc')
///messageデータを50件のみ取得します(新しい順なので最新50件)。
      ->take(50)
///idのみ抽出します(必要ないけど、動作軽くなるかなーと思って)
      ->pluck('id')
//最新50件のなかで最も古いメッセージのidを取得。これで$deleteidに最も古いメッセージのidを入れました。
      ->min();

      Message::where('group_id',$_POST['group_id'])
//$deleteidより古いメッセージは全て削除。
      ->where('id','<',$deleteid)->delete();
    }

このコードより前で$message = new Message;しました。つまり$messageは、Messageモデルのインスタンスです。group_idはAjaxのtype: "POST",で送信しているので、$_POST['group_id']で受け取ります。type: "GET",の場合やそもそもtype指定してない場合は$_GET['group_id']で受け取ってください。type指定しないと自動でtype: "GET",になるので。

tips

なぜ$messageがあるにもかかわらずif(){}内でわざわざDB::table('messages');しているのか。なぜ$messageを使用しないのか。
それは、$messageはcollectionだからです。Messageモデルのインスタンスなのでcollectionになります。
クエリビルダのメソッドを使いたいです。->orderBy()->take()->pluck()を使いたいです。DBのインスタンスじゃないとクエリビルダメソッドは使えません。
ちなみにuse Illuminate\Support\Facades\DB; //★クラスをインスタンス化してます。

また、$messageにクエリメソッドをしようすると、Object of class Illuminate\Database\Eloquent\Builder could not be converted to stringエラーが出ます。
一番下でまたクラス呼んでいますが、なぜそうしたか自分でもわかりません。昨日の自分に問い質したいです。
Message::where('group_id',$_POST['group_id'])
->where('id','<',$deleteid)->delete();

最後に

よく「ドキュメント読め」とか「オブジェクト指向を理解しろ」と言われますが、それが全てだと僕は思いません。
ドキュメントが理解できるようになったのは学習開始5ヶ月目とかですし、平均1日6時間とか勉強した挙句のそれです。
僕が要領悪いだけかもしれませんが、是非僕のような初心者の方の役に立てたら幸いです。