Mysql
 sql >> Base de données >  >> RDS >> Mysql

Système de notification utilisant php et mysql

Eh bien, cette question a 9 mois, donc je ne sais pas si OP a toujours besoin d'une réponse, mais en raison des nombreuses vues et de la prime savoureuse, j'aimerais également ajouter ma moutarde (dicton allemand..).

Dans cet article, je vais essayer de faire un exemple simple et expliqué sur la façon de commencer à créer un système de notification.

Modifier : Eh bien, cela s'est avéré bien, bien, bien plus long que ce à quoi je m'attendais. Je suis vraiment fatigué à la fin, je suis désolé.

WTLDR ;

Question 1 : avoir un drapeau sur chaque notification.

Question 2 : Stockez toujours chaque notification sous la forme d'un enregistrement unique dans votre base de données et regroupez-les lorsqu'elles sont demandées.

Structure

Je suppose que les notifications ressembleront à :

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

Derrière les rideaux, cela pourrait ressembler à ceci :

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

Remarque : Je ne recommande pas de regrouper les notifications dans la base de données, faites-le lors de l'exécution, cela rend les choses beaucoup plus flexibles.

  • Non lu
    Chaque notification doit avoir un indicateur pour indiquer si le destinataire a déjà ouvert la notification.
  • Destinataire
    Définit qui reçoit la notification.
  • Expéditeur
    Définit qui a déclenché la notification.
  • Tapez
    Au lieu d'avoir chaque message en texte brut dans votre base de données, créez des types. De cette façon, vous pouvez créer des gestionnaires spéciaux pour différents types de notification dans votre backend. Réduira la quantité de données stockées dans votre base de données et vous donnera encore plus de flexibilité, permettant une traduction facile des notifications, des modifications des messages passés, etc.
  • Référence
    La plupart des notifications auront une référence à un enregistrement de votre base de données ou de votre application.

Chaque système sur lequel j'ai travaillé avait un simple 1 à 1 relation de référence sur une notification, vous pouvez avoir un 1 à n gardez à l'esprit que je vais continuer mon exemple avec 1:1. Cela signifie également que je n'ai pas besoin d'un champ définissant le type d'objet référencé car il est défini par le type de notification.

Tableau SQL

Maintenant, lors de la définition d'une structure de table réelle pour SQL, nous prenons quelques décisions en termes de conception de base de données. J'irai avec la solution la plus simple qui ressemblera à ceci :

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

Ou pour les paresseux, la commande SQL de création de table pour cet exemple :

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;

Service PHP

Cette implémentation dépend entièrement des besoins de votre application, Remarque : Cet exemple n'est pas la norme d'or sur la façon de créer un système de notification en PHP.

Modèle de notification

Ceci est un exemple de modèle de base de la notification elle-même, rien d'extraordinaire juste les propriétés nécessaires et les méthodes abstraites messageForNotification et messageForNotifications nous nous attendions à être implémentés dans les différents types de notification.

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

Vous devrez ajouter un constructeur , getters , passeurs et ce genre de choses par vous-même dans votre propre style, je ne vais pas fournir un système de notification prêt à l'emploi.

Types de notifications

Vous pouvez maintenant créer une nouvelle Notification sous-classe pour chaque type. L'exemple suivant gérerait l'action similaire d'un commentaire :

  • Ray a aimé votre commentaire. (1 notification)
  • John et Jane ont aimé votre commentaire. (2 notifications)
  • Jane, Johnny, James et Jenny ont aimé votre commentaire. (4 notifications)
  • Jonny, James et 12 autres personnes ont aimé votre commentaire. (14 notifications)

Exemple d'implémentation :

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

Gestionnaire de notifications

Pour travailler avec vos notifications dans votre application, créez quelque chose comme un gestionnaire de notifications :

class NotificationManager
{
    protected $notificationAdapter;

    public function add(Notification $notification);

    public function markRead(array $notifications);

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

L'notificationAdapter propriété doit contenir la logique en communication directe avec votre backend de données dans le cas de cet exemple mysql.

Créer des notifications

Utiliser mysql déclencheurs n'est pas faux, car il n'y a pas de mauvaise solution. Ce qui fonctionne, fonctionne... Mais je recommande fortement de ne pas laisser la base de données gérer la logique de l'application.

Ainsi, dans le gestionnaire de notifications, vous voudrez peut-être faire quelque chose comme ceci :

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

Derrière le add méthode de notificationAdapter peut être une commande d'insertion mysql brute. L'utilisation de cette abstraction d'adaptateur vous permet de passer facilement de mysql à une base de données basée sur des documents comme mongodb ce qui aurait du sens pour un système de notification.

Le isDoublicate méthode sur notificationAdapter devrait simplement vérifier s'il existe déjà une notification avec le même recipient , sender , type et reference .

Je ne peux pas souligner assez qu'il ne s'agit que d'un exemple. (De plus, je dois vraiment raccourcir les prochaines étapes, ce message devient ridiculement long -.-)

Donc, en supposant que vous ayez une sorte de contrôleur avec une action lorsqu'un enseignant télécharge des devoirs :

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

Créera une notification pour chaque élève de l'enseignant lorsqu'il télécharge un nouveau devoir.

Lire les notifications

Vient maintenant la partie la plus difficile. Le problème avec le regroupement côté PHP est que vous devrez charger tous notifications de l'utilisateur actuel pour les regrouper correctement. Ce serait mauvais, eh bien si vous n'avez que quelques utilisateurs, ce ne serait probablement toujours pas un problème, mais cela ne le rend pas bon.

La solution simple consiste simplement à limiter le nombre de notifications demandées et à ne les regrouper que. Cela fonctionnera bien lorsqu'il n'y a pas beaucoup de notifications similaires (comme 3-4 sur 20). Mais disons que la publication d'un utilisateur / étudiant obtient une centaine de likes et que vous ne sélectionnez que les 20 dernières notifications. L'utilisateur ne verra alors que 20 personnes ont aimé sa publication, ce qui serait également sa seule notification.

Une solution "correcte" consisterait à regrouper les notifications déjà présentes dans la base de données et à ne sélectionner que quelques échantillons par groupe de notification. Il vous suffirait alors d'injecter le nombre réel dans vos messages de notification.

Vous n'avez probablement pas lu le texte ci-dessous, alors laissez-moi continuer avec un extrait :

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

Vous savez maintenant quelles notifications doivent exister pour l'utilisateur donné et combien de notifications le groupe contient.

Et maintenant la partie merdique. Je n'ai toujours pas trouvé de meilleur moyen de sélectionner un nombre limité de notifications pour chaque groupe sans effectuer une requête pour chaque groupe. Toutes les suggestions ici sont les bienvenues.

Alors je fais quelque chose comme :

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

Je vais maintenant continuer en supposant que le notificationAdapter s get La méthode implémente ce regroupement et renvoie un tableau comme celui-ci :

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

Parce que nous avons toujours au moins une notification dans notre groupe et que notre ordre préfère Non lu et Nouveau notifications, nous pouvons simplement utiliser la première notification comme exemple pour le rendu.

Donc, pour pouvoir travailler avec ces notifications groupées, nous avons besoin d'un nouvel objet :

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

Et enfin, nous pouvons réellement assembler la plupart des choses. Voici comment la fonction get sur le NotificationManager pourrait ressembler à :

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

Et vraiment enfin à l'intérieur d'une possible action du contrôleur :

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