sql >> データベース >  >> RDS >> Mysql

phpとmysqlを使用した通知システム

    さて、この質問は9か月前なので、OPがまだ回答を必要としているかどうかはわかりませんが、多くの意見とおいしい恵みのために、マスタードも追加したいと思います(ドイツ語)。

    >

    この投稿では、通知システムの構築を開始する方法について簡単に説明した例を作成しようと思います。

    編集: さて、これは私が予想していたよりもずっと長くなりました。結局本当に疲れました、ごめんなさい。

    WTLDR;

    質問1: すべての通知にフラグを付けます。

    質問2: それでも、すべての通知をデータベース内に1つのレコードとして保存し、要求されたときにグループ化します。

    構造

    通知は次のようになると思います:

    +---------------------------------------------+
    | ▣ James has uploaded new Homework: Math 1+1 |
    +---------------------------------------------+
    | ▣ Jane and John liked your comment: Im s... | 
    +---------------------------------------------+
    | ▢ The School is closed on independence day. |
    +---------------------------------------------+
    

    カーテンの後ろでは、これは次のようになります。

    +--------+-----------+--------+-----------------+-------------------------------------------+
    | unread | recipient | sender | type            | reference                                 |
    +--------+-----------+--------+-----------------+-------------------------------------------+
    | true   | me        | James  | homework.create | Math 1 + 1                                |
    +--------+-----------+--------+-----------------+-------------------------------------------+
    | true   | me        | Jane   | comment.like    | Im sick of school                         |
    +--------+-----------+--------+-----------------+-------------------------------------------+
    | true   | me        | John   | comment.like    | Im sick of school                         |
    +--------+-----------+--------+-----------------+-------------------------------------------+
    | false  | me        | system | message         | The School is closed on independence day. |
    +--------+-----------+--------+-----------------+-------------------------------------------+
    

    注: データベース内で通知をグループ化することはお勧めしません。実行時にこれを行うと、物事がはるかに柔軟になります。

    • 未読
      すべての通知には、受信者がすでに通知を開いているかどうかを示すフラグを付ける必要があります。
    • 受信者
      通知を受け取る人を定義します。
    • 送信者
      通知をトリガーしたユーザーを定義します。
    • タイプ
      データベース内のすべてのメッセージをプレーンテキストにする代わりに、タイプを作成します。このようにして、バックエンド内にさまざまな通知タイプ用の特別なハンドラーを作成できます。データベース内に保存されるデータの量を減らし、さらに柔軟性を高め、通知の簡単な翻訳、過去のメッセージの変更などを可能にします。
    • リファレンス
      ほとんどの通知には、データベースまたはアプリケーションのレコードへの参照があります。

    私が取り組んできたすべてのシステムには、単純な1対1がありました。 通知の参照関係では、1からnの場合があります 私は1:1で私の例を続けることを覚えておいてください。これは、通知タイプによって定義されるため、参照されるオブジェクトのタイプを定義するフィールドが必要ないことも意味します。

    SQLテーブル

    ここで、SQLの実際のテーブル構造を定義するときに、データベース設計に関していくつかの決定を行います。次のような最も単純なソリューションを使用します:

    +--------------+--------+---------------------------------------------------------+
    | column       | type   | description                                             |
    +--------------+--------+---------------------------------------------------------+
    | id           | int    | Primary key                                             |
    +--------------+--------+---------------------------------------------------------+
    | recipient_id | int    | The receivers user id.                                  |
    +--------------+--------+---------------------------------------------------------+
    | sender_id    | int    | The sender's user id.                                   |
    +--------------+--------+---------------------------------------------------------+
    | unread       | bool   | Flag if the recipient has already read the notification |
    +--------------+--------+---------------------------------------------------------+
    | type         | string | The notification type.                                  |
    +--------------+--------+---------------------------------------------------------+
    | parameters   | array  | Additional data to render different notification types. |
    +--------------+--------+---------------------------------------------------------+
    | reference_id | int    | The primary key of the referencing object.              |
    +--------------+--------+---------------------------------------------------------+
    | created_at   | int    | Timestamp of the notification creation date.            |
    +--------------+--------+---------------------------------------------------------+
    

    または、怠惰な人のために、 SQLcreatetableコマンド この例の場合:

    CREATE TABLE `notifications` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `recipient_id` int(11) NOT NULL,
      `sender_id` int(11) NOT NULL,
      `unread` tinyint(1) NOT NULL DEFAULT '1',
      `type` varchar(255) NOT NULL DEFAULT '',
      `parameters` text NOT NULL,
      `reference_id` int(11) NOT NULL,
      `created_at` int(11) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    PHPサービス

    この実装は、アプリケーションのニーズに完全に依存します。注: これは、PHPで通知システムを構築する方法のゴールデンスタンダードではない例です。

    通知モデル

    これは通知自体の基本モデルの例であり、必要なプロパティと抽象メソッドmessageForNotificationだけを凝らしたものではありません。 およびmessageForNotifications さまざまな通知タイプで実装されることを期待していました。

    abstract class Notification
    {
        protected $recipient;
        protected $sender;
        protected $unread;
        protected $type;
        protected $parameters;
        protected $referenceId;
        protected $createdAt;
    
        /**
         * Message generators that have to be defined in subclasses
         */
        public function messageForNotification(Notification $notification) : string;
        public function messageForNotifications(array $notifications) : string;
    
        /**
         * Generate message of the current notification.
         */ 
        public function message() : string
        {
            return $this->messageForNotification($this);
        }
    }
    

    コンストラクタを追加する必要があります 、ゲッターセッター そして、そのようなものはあなた自身のスタイルで、私はすぐに使える通知システムを提供するつもりはありません。

    通知の種類

    これで、新しいNotificationを作成できます すべてのタイプのサブクラス。次の例では、 like action を処理します コメントの:

    • レイはあなたのコメントを高く評価しました。 (1件の通知)
    • ジョンとジェーンはあなたのコメントを気に入りました。 (2つの通知)
    • ジェーン、ジョニー、ジェームス、ジェニーがあなたのコメントを気に入りました。 (4つの通知)
    • ジョニー、ジェームス、その他12人があなたのコメントを気に入りました。 (14件の通知)

    実装例:

    namespace Notification\Comment;
    
    class CommentLikedNotification extends \Notification
    {
        /**
         * Generate a message for a single notification
         * 
         * @param Notification              $notification
         * @return string 
         */
        public function messageForNotification(Notification $notification) : string 
        {
            return $this->sender->getName() . 'has liked your comment: ' . substr($this->reference->text, 0, 10) . '...'; 
        }
    
        /**
         * Generate a message for a multiple notifications
         * 
         * @param array              $notifications
         * @return string 
         */
        public function messageForNotifications(array $notifications, int $realCount = 0) : string 
        {
            if ($realCount === 0) {
                $realCount = count($notifications);
            }
    
            // when there are two 
            if ($realCount === 2) {
                $names = $this->messageForTwoNotifications($notifications);
            }
            // less than five
            elseif ($realCount < 5) {
                $names = $this->messageForManyNotifications($notifications);
            }
            // to many
            else {
                $names = $this->messageForManyManyNotifications($notifications, $realCount);
            }
    
            return $names . ' liked your comment: ' . substr($this->reference->text, 0, 10) . '...'; 
        }
    
        /**
         * Generate a message for two notifications
         *
         *      John and Jane has liked your comment.
         * 
         * @param array              $notifications
         * @return string 
         */
        protected function messageForTwoNotifications(array $notifications) : string 
        {
            list($first, $second) = $notifications;
            return $first->getName() . ' and ' . $second->getName(); // John and Jane
        }
    
        /**
         * Generate a message many notifications
         *
         *      Jane, Johnny, James and Jenny has liked your comment.
         * 
         * @param array              $notifications
         * @return string 
         */
        protected function messageForManyNotifications(array $notifications) : string 
        {
            $last = array_pop($notifications);
    
            foreach($notifications as $notification) {
                $names .= $notification->getName() . ', ';
            }
    
            return substr($names, 0, -2) . ' and ' . $last->getName(); // Jane, Johnny, James and Jenny
        }
    
        /**
         * Generate a message for many many notifications
         *
         *      Jonny, James and 12 other have liked your comment.
         * 
         * @param array              $notifications
         * @return string 
         */
        protected function messageForManyManyNotifications(array $notifications, int $realCount) : string 
        {
            list($first, $second) = array_slice($notifications, 0, 2);
    
            return $first->getName() . ', ' . $second->getName() . ' and ' .  $realCount . ' others'; // Jonny, James and 12 other
        }
    }
    

    通知マネージャー

    アプリケーション内で通知を操作するには、通知マネージャーのようなものを作成します。

    class NotificationManager
    {
        protected $notificationAdapter;
    
        public function add(Notification $notification);
    
        public function markRead(array $notifications);
    
        public function get(User $user, $limit = 20, $offset = 0) : array;
    }
    

    notificationAdapter この例のmysqlの場合、プロパティにはデータバックエンドと直接通信するロジックが含まれている必要があります。

    通知の作成

    mysqlを使用する 間違った解決策はないので、トリガーは間違っていません。 機能するもの、機能するもの.. ただし、データベースにアプリケーションロジックを処理させないことを強くお勧めします。

    したがって、通知マネージャー内では、次のようなことを行うことができます。

    public function add(Notification $notification)
    {
        // only save the notification if no possible duplicate is found.
        if (!$this->notificationAdapter->isDoublicate($notification))
        {
            $this->notificationAdapter->add([
                'recipient_id' => $notification->recipient->getId(),
                'sender_id' => $notification->sender->getId()
                'unread' => 1,
                'type' => $notification->type,
                'parameters' => $notification->parameters,
                'reference_id' => $notification->reference->getId(),
                'created_at' => time(),
            ]);
        }
    }
    

    addの背後 notificationAdapterのメソッド 生のmysql挿入コマンドにすることができます。このアダプター抽象化を使用すると、mysqlから mongodbのようなドキュメントベースのデータベースに簡単に切り替えることができます。 これは通知システムにとって意味があります。

    isDoublicate notificationAdapterのメソッド 同じrecipientの通知がすでにあるかどうかを確認するだけです。 、sendertype およびreference

    これが単なる例であることを十分に指摘することはできません。 (また、この投稿が途方もなく長くなっている次のステップを本当に短くする必要があります-.-)

    したがって、教師が宿題をアップロードするときにアクションを実行するある種のコントローラーがあると仮定します。

    function uploadHomeworkAction(Request $request)
    {
        // handle the homework and have it stored in the var $homework.
    
        // how you handle your services is up to you...
        $notificationManager = new NotificationManager;
    
        foreach($homework->teacher->students as $student)
        {
            $notification = new Notification\Homework\HomeworkUploadedNotification;
            $notification->sender = $homework->teacher;
            $notification->recipient = $student;
            $notification->reference = $homework;
    
            // send the notification
            $notificationManager->add($notification);
        }
    }
    

    新しい宿題をアップロードすると、すべての教師の生徒に通知が作成されます。

    通知を読む

    今、難しい部分が来ます。 PHP側でのグループ化の問題は、すべてをロードする必要があることです。 それらを正しくグループ化するための現在のユーザーの通知。これは悪いことです。ユーザーが数人しかない場合でも問題はないでしょうが、それでも問題はありません。

    簡単な解決策は、要求される通知の数を制限し、これらのみをグループ化することです。これは、同様の通知が多くない場合(20あたり3〜4など)に正常に機能します。しかし、ユーザー/学生の投稿が約100のいいねを取得し、最後の20の通知のみを選択したとします。ユーザーには、20人が自分の投稿を高く評価したことが表示されるだけで、それが彼の唯一の通知になります。

    「正しい」解決策は、データベースにすでに存在する通知をグループ化し、通知グループごとに一部のサンプルのみを選択することです。通知メッセージに実際の数を挿入するだけで済みます。

    以下のテキストを読んでいない可能性があるので、スニペットを続けましょう:

    select *, count(*) as count from notifications
    where recipient_id = 1
    group by `type`, `reference_id`
    order by created_at desc, unread desc
    limit 20
    

    これで、特定のユーザーに対してどのような通知を表示する必要があるか、グループに含まれる通知の数がわかりました。

    そして今、くだらない部分。それでも、グループごとにクエリを実行せずに、グループごとに限られた数の通知を選択するためのより良い方法を見つけることができませんでした。 ここでのすべての提案は大歓迎です。

    だから私は次のようなことをします:

    $notifcationGroups = [];
    
    foreach($results as $notification)
    {
        $notifcationGroup = ['count' => $notification['count']];
    
        // when the group only contains one item we don't 
        // have to select it's children
        if ($notification['count'] == 1)
        {
            $notifcationGroup['items'] = [$notification];
        }
        else
        {
            // example with query builder
            $notifcationGroup['items'] = $this->select('notifications')
                ->where('recipient_id', $recipient_id)
                ->andWehere('type', $notification['type'])
                ->andWhere('reference_id', $notification['reference_id'])
                ->limit(5);
        }
    
        $notifcationGroups[] = $notifcationGroup;
    }
    

    notificationAdapterを想定し続けます ■get メソッドはこのグループ化を実装し、次のような配列を返します:

    [
        {
            count: 12,
            items: [Note1, Note2, Note3, Note4, Note5] 
        },
        {
            count: 1,
            items: [Note1] 
        },
        {
            count: 3,
            items: [Note1, Note2, Note3] 
        }
    ]
    

    グループには常に少なくとも1つの通知があり、注文では未読が優先されるためです。 および新規 通知最初の通知をレンダリングのサンプルとして使用できます。

    したがって、これらのグループ化された通知を処理できるようにするには、新しいオブジェクトが必要です。

    class NotificationGroup
    {
        protected $notifications;
    
        protected $realCount;
    
        public function __construct(array $notifications, int $count)
        {
            $this->notifications = $notifications;
            $this->realCount = $count;
        }
    
        public function message()
        {
            return $this->notifications[0]->messageForNotifications($this->notifications, $this->realCount);
        }
    
        // forward all other calls to the first notification
        public function __call($method, $arguments)
        {
            return call_user_func_array([$this->notifications[0], $method], $arguments);
        }
    }
    

    そして最後に、実際にほとんどのものをまとめることができます。これは、NotificationManagerのget関数の方法です。 次のようになります:

    public function get(User $user, $limit = 20, $offset = 0) : array
    {
        $groups = [];
    
        foreach($this->notificationAdapter->get($user->getId(), $limit, $offset) as $group)
        {
            $groups[] = new NotificationGroup($group['notifications'], $group['count']);
        }
    
        return $gorups;
    }
    

    そして最後に、可能なコントローラーアクションの内部:

    public function viewNotificationsAction(Request $request)
    {
        $notificationManager = new NotificationManager;
    
        foreach($notifications = $notificationManager->get($this->getUser()) as $group)
        {
            echo $group->unread . ' | ' . $group->message() . ' - ' . $group->createdAt() . "\n"; 
        }
    
        // mark them as read 
        $notificationManager->markRead($notifications);
    }
    


    1. mysql:postfix-searchの効率的な方法('%text'別名プレフィックスワイルドカードなど)?

    2. Java:com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:データベースサーバーへの接続を作成できませんでした

    3. ピアツーピア貸付プラットフォームデータモデル

    4. テーブルから2番目に大きいまたは3番目に大きいエントリを取得する方法