Mysql
 sql >> Database >  >> RDS >> Mysql

Sistema di notifica tramite php e mysql

Bene, questa domanda ha 9 mesi, quindi non sono sicuro che OP abbia ancora bisogno di una risposta, ma a causa delle molte visualizzazioni e della gustosa generosità vorrei aggiungere anche la mia senape (detto tedesco..).

In questo post cercherò di fare un semplice esempio spiegato su come iniziare a costruire un sistema di notifica.

Modifica: Bene, ok, questo si è rivelato molto, molto, molto più lungo di quanto mi aspettassi. Alla fine mi sono davvero stancato, mi dispiace.

WTLDR;

Domanda 1: metti un flag su ogni notifica.

Domanda 2: Memorizza comunque ogni notifica come un singolo record all'interno del tuo database e raggruppale quando vengono richieste.

Struttura

Presumo che le notifiche assomiglieranno a:

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

Dietro le tende potrebbe sembrare qualcosa del genere:

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

Nota: Non consiglio di raggruppare le notifiche all'interno del database, fallo in runtime questo mantiene le cose molto più flessibili.

  • Non letto
    Ogni notifica dovrebbe avere un flag per indicare se il destinatario ha già aperto la notifica.
  • Destinatario
    Definisce chi riceve la notifica.
  • Mittente
    Definisce chi ha attivato la notifica.
  • Digita
    Invece di avere tutti i messaggi in testo normale all'interno del database, crea tipi. In questo modo puoi creare gestori speciali per diversi tipi di notifica all'interno del tuo back-end. Ridurrà la quantità di dati archiviati all'interno del tuo database e ti darà ancora più flessibilità, consentendo una facile traduzione delle notifiche, modifiche dei messaggi passati ecc.
  • Riferimento
    La maggior parte delle notifiche avrà un riferimento a un record nel tuo database o nella tua applicazione.

Ogni sistema su cui ho lavorato aveva un semplice 1 a 1 relazione di riferimento su una notifica, potresti avere un valore da 1 a n tieni presente che continuerò il mio esempio con 1:1. Ciò significa anche che non ho bisogno di un campo che definisca a quale tipo di oggetto si fa riferimento perché questo è definito dal tipo di notifica.

Tabella SQL

Ora, quando si definisce una vera struttura di tabelle per SQL, arriviamo ad alcune decisioni in termini di progettazione del database. Andrò con la soluzione più semplice che assomiglierà a questa:

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

Oppure, per i più pigri, il comando SQL create table per questo esempio:

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;

Servizio PHP

Questa implementazione dipende completamente dalle esigenze della tua applicazione, Nota: Questo è un esempio non lo standard d'oro su come costruire un sistema di notifica in PHP.

Modello di notifica

Questo è un modello base di esempio della notifica stessa, niente di speciale solo le proprietà necessarie e i metodi astratti messageForNotification e messageForNotifications ci aspettavamo di essere implementato nei diversi tipi di notifica.

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

Dovrai aggiungere un costruttore , gettatori , incastonatori e quel genere di cose da soli nel tuo stile, non ho intenzione di fornire un sistema di notifica pronto per l'uso.

Tipi di notifica

Ora puoi creare una nuova Notification sottoclasse per ogni tipo. Questo esempio seguente gestirebbe l'azione simile di un commento:

  • A Ray è piaciuto il tuo commento. (1 notifica)
  • A John e Jane è piaciuto il tuo commento. (2 notifiche)
  • A Jane, Johnny, James e Jenny è piaciuto il tuo commento. (4 notifiche)
  • Il tuo commento è piaciuto a Jonny, James e altre 12 persone. (14 notifiche)

Esempio di implementazione:

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

Gestione notifiche

Per lavorare con le tue notifiche all'interno della tua applicazione, crea qualcosa come un gestore delle notifiche:

class NotificationManager
{
    protected $notificationAdapter;

    public function add(Notification $notification);

    public function markRead(array $notifications);

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

Il notificationAdapter dovrebbe contenere la logica in comunicazione diretta con il tuo backend di dati nel caso di questo esempio mysql.

Creazione di notifiche

Usando mysql trigger non è sbagliato, perché non esiste una soluzione sbagliata. Ciò che funziona, funziona.. Ma consiglio vivamente di non lasciare che il database gestisca la logica dell'applicazione.

Quindi all'interno del gestore delle notifiche potresti voler fare qualcosa del genere:

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

Dietro il add metodo del notificationAdapter può essere un comando di inserimento di MySQL grezzo. L'utilizzo di questa astrazione dell'adattatore ti consente di passare facilmente da mysql a un database basato su documenti come mongodb che avrebbe senso per un sistema di notifica.

Il isDoublicate metodo su notificationAdapter dovrebbe semplicemente verificare se esiste già una notifica con lo stesso recipient , sender , type e reference .

Non posso sottolineare abbastanza che questo è solo un esempio. (Inoltre devo davvero abbreviare i prossimi passi questo post sta diventando ridicolmente lungo -.-)

Quindi, supponendo che tu abbia una sorta di controller con un'azione quando un insegnante carica i compiti:

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

Creerà una notifica per ogni studente dell'insegnante quando carica un nuovo compito.

Lettura delle notifiche

Ora arriva la parte difficile. Il problema con il raggruppamento sul lato PHP è che dovrai caricare tutto notifiche dell'utente corrente per raggrupparli correttamente. Questo sarebbe un male, beh, se hai solo pochi utenti probabilmente non sarebbe ancora un problema, ma questo non lo rende buono.

La soluzione semplice è semplicemente limitare il numero di notifiche richieste e raggrupparle solo. Funzionerà bene quando non ci sono molte notifiche simili (come 3-4 per 20). Ma diciamo che il post di un utente/studente riceve circa un centinaio di Mi piace e tu selezioni solo le ultime 20 notifiche. L'utente vedrà quindi solo che 20 persone hanno apprezzato il suo post, anche questa sarebbe la sua unica notifica.

Una soluzione "corretta" sarebbe raggruppare le notifiche già presenti nel database e selezionare solo alcuni campioni per gruppo di notifica. Quindi dovresti semplicemente iniettare il conteggio reale nei tuoi messaggi di notifica.

Probabilmente non hai letto il testo qui sotto, quindi lasciami continuare con uno snippet:

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

Ora sai quali notifiche dovrebbero essere disponibili per un determinato utente e quante notifiche contiene il gruppo.

E ora la parte di merda. Non riuscivo ancora a trovare un modo migliore per selezionare un numero limitato di notifiche per ciascun gruppo senza eseguire una query per ogni gruppo. Tutti i suggerimenti qui sono molto ben accetti.

Quindi faccio qualcosa del tipo:

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

Continuerò ora supponendo che il notificationAdapter s get il metodo implementa questo raggruppamento e restituisce un array come questo:

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

Perché abbiamo sempre almeno una notifica nel nostro gruppo e il nostro ordine preferisce Non letto e Nuovo notifiche possiamo semplicemente utilizzare la prima notifica come esempio per il rendering.

Quindi per poter lavorare con queste notifiche raggruppate abbiamo bisogno di un nuovo oggetto:

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

E finalmente possiamo mettere insieme la maggior parte delle cose. Ecco come funziona la funzione get su NotificationManager potrebbe assomigliare a:

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

E davvero, finalmente, all'interno di una possibile azione del controller:

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