Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Benachrichtigungssystem mit PHP und MySQL

Nun, diese Frage ist 9 Monate alt, daher bin ich mir nicht sicher, ob OP noch eine Antwort benötigt, aber aufgrund der vielen Aufrufe und des leckeren Kopfgeldes möchte ich auch meinen Senf hinzufügen>

In diesem Beitrag werde ich versuchen, ein einfaches, erklärtes Beispiel zu geben, wie man mit dem Aufbau eines Benachrichtigungssystems beginnt.

Bearbeiten: Nun gut, das ist viel, viel, viel länger geworden, als ich erwartet hatte. Am Ende wurde ich wirklich müde, tut mir leid.

WTLDR;

Frage 1: haben ein Flag auf jeder Benachrichtigung.

Frage 2: Speichern Sie dennoch jede Benachrichtigung als einzelnen Datensatz in Ihrer Datenbank und gruppieren Sie sie, wenn sie angefordert werden.

Struktur

Ich gehe davon aus, dass die Benachrichtigungen in etwa so aussehen:

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

Hinter den Kulissen könnte das etwa so aussehen:

+--------+-----------+--------+-----------------+-------------------------------------------+
| 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. |
+--------+-----------+--------+-----------------+-------------------------------------------+

Hinweis: Ich empfehle nicht, die Benachrichtigungen in der Datenbank zu gruppieren, tun Sie dies zur Laufzeit, wodurch die Dinge viel flexibler bleiben.

  • Ungelesen
    Jede Benachrichtigung sollte ein Flag haben, um anzuzeigen, ob der Empfänger die Benachrichtigung bereits geöffnet hat.
  • Empfänger
    Legt fest, wer die Benachrichtigung erhält.
  • Absender
    Definiert, wer die Benachrichtigung ausgelöst hat.
  • Typ
    Anstatt jede Nachricht im Klartext in Ihrer Datenbank zu haben, erstellen Sie Typen. Auf diese Weise können Sie in Ihrem Backend spezielle Handler für verschiedene Benachrichtigungstypen erstellen. Reduziert die in Ihrer Datenbank gespeicherte Datenmenge und gibt Ihnen noch mehr Flexibilität, ermöglicht eine einfache Übersetzung von Benachrichtigungen, Änderungen früherer Nachrichten usw.
  • Referenz
    Die meisten Benachrichtigungen enthalten einen Verweis auf einen Eintrag in Ihrer Datenbank oder Ihrer Anwendung.

Jedes System, an dem ich gearbeitet habe, hatte ein einfaches 1 zu 1 Referenzbeziehung auf einer Benachrichtigung haben Sie möglicherweise eine 1 bis n bedenken Sie, dass ich mein Beispiel 1:1 weiterführen werde. Das bedeutet auch, dass ich kein Feld benötige, das definiert, auf welchen Objekttyp verwiesen wird, da dies durch den Benachrichtigungstyp definiert wird.

SQL-Tabelle

Wenn wir nun eine echte Tabellenstruktur für SQL definieren, treffen wir einige Entscheidungen in Bezug auf das Datenbankdesign. Ich werde mit der einfachsten Lösung gehen, die in etwa so aussehen wird:

+--------------+--------+---------------------------------------------------------+
| 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.            |
+--------------+--------+---------------------------------------------------------+

Oder für die faulen Leute der SQL create table Befehl für dieses Beispiel:

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-Dienst

Diese Implementierung hängt vollständig von den Anforderungen Ihrer Anwendung ab, Hinweis: Dies ist ein Beispiel, nicht der goldene Standard, wie man ein Benachrichtigungssystem in PHP erstellt.

Benachrichtigungsmodell

Dies ist ein beispielhaftes Basismodell der Benachrichtigung selbst, nichts Besonderes, nur die benötigten Eigenschaften und die abstrakten Methoden messageForNotification und messageForNotifications Wir haben erwartet, dass sie in den verschiedenen Benachrichtigungstypen implementiert werden.

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);
    }
}

Sie müssen einen Konstruktor hinzufügen , Getter , Setzer und diese Art von Sachen selbst in Ihrem eigenen Stil, ich werde kein fertiges Benachrichtigungssystem bereitstellen.

Benachrichtigungstypen

Jetzt können Sie eine neue Notification erstellen Unterklasse für jeden Typ. Das folgende Beispiel behandelt die Gefällt mir-Aktion eines Kommentars:

  • Ray hat Ihren Kommentar gemocht. (1 Benachrichtigung)
  • John und Jane mochten Ihren Kommentar. (2 Benachrichtigungen)
  • Jane, Johnny, James und Jenny mochten deinen Kommentar. (4 Benachrichtigungen)
  • Jonny, James und 12 anderen gefiel dein Kommentar. (14 Benachrichtigungen)

Beispielimplementierung:

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
    }
}

Benachrichtigungsmanager

Um mit Ihren Benachrichtigungen in Ihrer Anwendung zu arbeiten, erstellen Sie so etwas wie einen Benachrichtigungsmanager:

class NotificationManager
{
    protected $notificationAdapter;

    public function add(Notification $notification);

    public function markRead(array $notifications);

    public function get(User $user, $limit = 20, $offset = 0) : array;
}

Der notificationAdapter Die Eigenschaft sollte im Fall dieses mysql-Beispiels die Logik in direkter Kommunikation mit Ihrem Daten-Backend enthalten.

Benachrichtigungen erstellen

Mit mysql triggers ist nicht falsch, denn es gibt keine falsche Lösung. Was funktioniert, funktioniert... Aber ich empfehle dringend, die Anwendungslogik nicht von der Datenbank verarbeiten zu lassen.

Innerhalb des Benachrichtigungsmanagers möchten Sie vielleicht so etwas tun:

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(),
        ]);
    }
}

Hinter dem add Methode des notificationAdapter kann ein reiner MySQL-Einfügebefehl sein. Die Verwendung dieser Adapterabstraktion ermöglicht es Ihnen, einfach von MySQL zu einer dokumentenbasierten Datenbank wie mongodb zu wechseln was für ein Benachrichtigungssystem Sinn machen würde.

Der isDoublicate -Methode auf dem notificationAdapter sollte einfach prüfen, ob es bereits eine Benachrichtigung mit demselben recipient gibt , sender , type und reference .

Ich kann nicht genug darauf hinweisen, dass dies nur ein Beispiel ist. (Außerdem muss ich die nächsten Schritte wirklich kürzen, dieser Beitrag wird lächerlich lang -.-)

Angenommen, Sie haben eine Art Controller mit einer Aktion, wenn ein Lehrer Hausaufgaben hochlädt:

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);
    }
}

Erstellt eine Benachrichtigung für jeden Schüler eines Lehrers, wenn er eine neue Hausaufgabe hochlädt.

Lesen der Benachrichtigungen

Jetzt kommt der schwierige Teil. Das Problem mit der Gruppierung auf der PHP-Seite ist, dass Sie all laden müssen Benachrichtigungen des aktuellen Benutzers, um sie richtig zu gruppieren. Das wäre schlecht, nun, wenn Sie nur wenige Benutzer haben, wäre es wahrscheinlich immer noch kein Problem, aber das macht es nicht gut.

Die einfache Lösung besteht darin, einfach die Anzahl der angeforderten Benachrichtigungen zu begrenzen und nur diese zu gruppieren. Dies funktioniert gut, wenn es nicht viele ähnliche Benachrichtigungen gibt (z. B. 3-4 pro 20). Aber nehmen wir an, der Beitrag eines Benutzers / Studenten erhält ungefähr hundert Likes und Sie wählen nur die letzten 20 Benachrichtigungen aus. Der Benutzer sieht dann nur, dass 20 Personen seinen Beitrag geliked haben, auch das wäre seine einzige Benachrichtigung.

Eine „richtige“ Lösung wäre, die bereits in der Datenbank vorhandenen Meldungen zu gruppieren und nur einige Beispiele pro Meldungsgruppe auszuwählen. Dann müssten Sie nur die tatsächliche Anzahl in Ihre Benachrichtigungsmeldungen einfügen.

Wahrscheinlich haben Sie den folgenden Text nicht gelesen, also lassen Sie mich mit einem Ausschnitt fortfahren:

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

Jetzt wissen Sie, welche Benachrichtigungen für den angegebenen Benutzer verfügbar sein sollten und wie viele Benachrichtigungen die Gruppe enthält.

Und jetzt der beschissene Teil. Ich konnte immer noch keinen besseren Weg finden, um eine begrenzte Anzahl von Benachrichtigungen für jede Gruppe auszuwählen, ohne eine Abfrage für jede Gruppe durchzuführen. Alle Vorschläge hier sind sehr willkommen.

Also mache ich so etwas wie:

$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;
}

Ich gehe jetzt weiterhin davon aus, dass der notificationAdapter s get -Methode implementiert diese Gruppierung und gibt ein Array wie dieses zurück:

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

Weil wir immer mindestens eine Benachrichtigung in unserer Gruppe haben und unsere Sortierung Ungelesen bevorzugt und Neu Benachrichtigungen können wir einfach die erste Benachrichtigung als Beispiel für das Rendern verwenden.

Um also mit diesen gruppierten Benachrichtigungen arbeiten zu können, brauchen wir ein neues Objekt:

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);
    }
}

Und schließlich können wir das meiste Zeug tatsächlich zusammenstellen. So funktioniert die Get-Funktion auf dem NotificationManager könnte so aussehen:

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;
}

Und wirklich endlich in einer möglichen Controller-Aktion:

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);
}