web-dev-qa-db-fra.com

Système de notification utilisant php et mysql

Je voulais implémenter un système de notification pour notre école, c'est une application web php/mysql qui n'est pas ouverte au public, donc elle ne reçoit pas beaucoup de trafic. "500-1000 visiteurs par jour".

1. Mon approche initiale utilisait des déclencheurs MYSQL:

J'ai utilisé un Mysql AFTER INSERT trigger pour ajouter des enregistrements à une table nommée notifications. Quelque chose comme.

'CREATE TRIGGER `notify_new_homwork` AFTER INSERT ON `homeworks`
 FOR EACH ROW INSERT INTO `notifications` 
    ( `from_id`, `note`, `class_id`) 
 VALUES 
    (new.user_id, 
        concat('A New homework Titled: "',left(new.title,'50'),
        '".. was added' )
    ,new.subject_id , 11);'

Ce genre de magie noire a très bien fonctionné, mais je ne pouvais pas savoir si cette notification était nouvelle "afin d'indiquer le nombre de nouvelles notifications pour l'utilisateur". donc j'ai ajouté une page nommée notifications.

Les notifications sont récupérées avec quelque chose comme

SELECT n.* from notifications n 
JOIN user_class on user_class.class_id = n.class_id where user_class.user_id = X;

Remarque: la table user_class lie l'utilisateur à la classe "user_id, class_id, subject_id" -subject est null sauf si l'utilisateur est un enseignant '

Maintenant mes prochains défis sont.

  1. comment garder une trace des nouvelles notifications par rapport aux anciennes notifications par utilisateur?
  2. comment puis-je regrouper les notifications similaires à l'utilisateur sur une seule ligne?

par exemple, si 2 utilisateurs ont commenté quelque chose, n'insérez pas une nouvelle ligne, mettez à jour l'ancienne avec quelque chose comme 'userx et une autre commentée sur hw'.

Merci beaucoup

Éditer

Comme indiqué dans la réponse ci-dessous, pour définir un indicateur lu/non lu sur une ligne, vous devez définir une ligne pour chaque élève, et pas seulement une ligne pour toute la classe. Ce qui signifie que vous devez modifier le déclencheur comme suit:

insert into notifications (from_id,note,student_id,isread)
select new.user_id,new.note,user_id,'0' from user_class where user_class.class_id = new.class_id group by user_class.user_id
39
Zalaboza

Eh bien, cette question date de 9 mois et je ne suis pas sûr que OP ait encore besoin d’une réponse, mais à cause des nombreux points de vue et de la généreuse récompense, j’aimerais également ajouter ma moutarde (proverbe allemand ...).

Dans cet article, je vais essayer de donner un exemple simple et explicite sur la manière de commencer à créer un système de notification.

Edit: Bien ok, cela s'est avéré 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: Enregistrez toujours chaque notification en tant qu’enregistrement unique dans votre base de données et regroupez-les lorsqu’elles sont demandées.


Structure

Je suppose que les notifications ressembleront à quelque chose comme:

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

Note: Je ne recommande pas de grouper les notifications dans la base de données, faites-le à l'exécution, cela rend les choses beaucoup plus souples.

  • non l
    Chaque notification devrait avoir un drapeau 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.
  • Type
    Au lieu d’avoir chaque type de 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é, vous permettant de traduire facilement les notifications, les 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 je travaillais avait une simple relation de référence 1 à 1 sur une notification, vous pourriez avoir une 1 à n garder en gardant à 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 celui-ci est défini par le type de notification.

Table SQL

Maintenant, lors de la définition d'une structure de table réelle pour SQL, nous prenons quelques décisions concernant la conception de la base de données. Je vais aller avec la solution la plus simple qui ressemblera à quelque chose comme 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 fainéants la commande SQL create table de 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: Ceci est un exemple et non la norme d'or pour la construction d'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 ne présente les propriétés nécessaires et les méthodes abstraites messageForNotification et messageForNotifications que nous attendions d'être implémentées 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 , setters 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 à utiliser.

Types de notification

Vous pouvez maintenant créer une nouvelle sous-classe Notification pour chaque type. Cet exemple suivant traitera l'action semblable à 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 notification

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

La propriété notificationAdapter devrait contenir la logique en communication directe avec votre serveur de données dans le cas de cet exemple mysql.

Création de notifications

L'utilisation de mysql triggers n'est pas fausse, 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.

Donc, 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 la méthode add de la notificationAdapter peut être une commande d'insertion mysql brute. En utilisant cet adaptateur, l'abstraction vous permet de passer facilement de mysql à une base de données basée sur des documents telle que mongodb , ce qui serait logique pour un système de notification.

La méthode isDoublicate sur le 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 dire assez que ce n'est qu'un exemple. (De plus, je dois vraiment raccourcir les prochaines étapes, cet article devient ridiculement long -.-)


Donc, en supposant que vous ayez une sorte de contrôleur avec une action lorsqu'un enseignant télécharge ses 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ée une notification pour chaque élève de l'enseignant lorsqu'il télécharge un nouveau travail.

Lire les notifications

Maintenant vient la partie difficile. Le problème avec le groupement du côté PHP est que vous devrez charger tous les notifications de l'utilisateur actuel pour les grouper correctement. Ce serait mauvais si vous n’ayant que peu d’utilisateurs, ce n’est 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 à les regrouper. Cela fonctionnera bien s'il n'y a pas beaucoup de notifications similaires (comme 3-4 sur 20). Mais disons que la publication d’un utilisateur/étudiant reçoit une centaine de "j'aime" et que vous ne sélectionnez que les 20 dernières notifications. L'utilisateur ne verra alors que 20 personnes ont aimé son message et qu'il s'agisse de sa seule notification.

Une solution "correcte" serait de regrouper les notifications déjà présentes dans la base de données et de ne sélectionner que quelques échantillons par groupe de notifications. Il vous suffira 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

Maintenant, vous savez quelles notifications doivent être disponibles pour l'utilisateur donné et combien de notifications le groupe contient.

Et maintenant la partie de merde. Je ne pouvais toujours pas trouver un meilleur moyen de sélectionner un nombre limité de notifications pour chaque groupe sans faire 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 la méthode notificationAdapters get 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 nos commandes préfèrent 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 finalement, nous pouvons rassembler la plupart des éléments. Voici comment la fonction get sur le NotificationManager pourrait ressembler à ceci:

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 finalement dans une action possible 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);
}
125
Mario

Répondre:

  1. Introduisez une variable lue/non lue dans la notification. Vous pouvez alors extraire uniquement les notifications non lues en faisant ... WHERE status = 'UNREAD' dans votre SQL.

  2. Vous ne pouvez pas vraiment ... vous voulez pousser cette notification. Ce que vous pouvez faire, c'est quand même les agréger en utilisant GROUP BY. Vous voudrez probablement grouper sur quelque chose d'unique, comme un nouveau devoir, donc quelque chose comme: GROUP BY homework.id

2
geggleto

Vous pouvez résoudre le problème en créant une table NotificationsRead, contenant l'ID de l'utilisateur et l'ID de la notification que l'utilisateur souhaitait marquer comme lue. (De cette façon, vous pouvez séparer chaque élève et chaque enseignant.) Ensuite, vous pouvez joindre cette table à la table de votre notification et vous saurez si elle doit être considérée comme ancienne ou nouvelle.

la réponse de geggleto était exacte à propos de la deuxième partie, vous pouvez saisir les notifications avec SELECT *, COUNT(*) AS counter WHERE ... GROUP BY 'type', puis vous saurez combien de types du même type vous avez là et vous pouvez préparer le 'userx et 1 autre commenté sur hw' sur vue.

Je vous suggère également de ne pas stocker le texte entier que vous souhaitez afficher, mais plutôt les informations requises, telles que: from_id, class_id, type, name, etc. - vous pourrez ainsi modifier plus facilement les mécanismes ultérieurement si vous en avez besoin. avoir à stocker moins.

1
Razor_alpha