sql >> Databasteknik >  >> RDS >> Mysql

Aviseringssystem som använder php och mysql

Den här frågan är 9 månader gammal så jag är inte säker på om OP fortfarande behöver ett svar men på grund av de många åsikterna och den välsmakande belöningen skulle jag också vilja lägga till min senap (tyskt ordspråk...).

I det här inlägget ska jag försöka göra ett enkelt förklarat exempel på hur man börjar bygga ett aviseringssystem.

Redigera: Okej, det här blev så mycket, mycket längre än jag förväntade mig. Jag blev riktigt trött till slut, jag är ledsen.

WTLDR;

Fråga 1: ha en flagga på varje avisering.

Fråga 2: Lagra fortfarande varje avisering som en enda post i din databas och gruppera dem när de efterfrågas.

Struktur

Jag antar att aviseringarna kommer att se ut ungefär så här:

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

Bakom gardinerna kan det se ut ungefär så här:

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

Obs! Jag rekommenderar inte att gruppera aviseringarna i databasen, gör det under körning, detta håller saker mycket mer flexibla.

  • Oläst
    Varje avisering bör ha en flagga för att indikera om mottagaren redan har öppnat meddelandet.
  • Mottagare
    Definierar vem som får meddelandet.
  • Avsändare
    Definierar vem som utlöste meddelandet.
  • Typ
    Skapa typer i stället för att ha alla meddelanden i vanlig text i din databas. På så sätt kan du skapa speciella hanterare för olika aviseringstyper i din backend. Kommer att minska mängden data som lagras i din databas och ger dig ännu mer flexibilitet, möjliggör enkel översättning av meddelanden, ändringar av tidigare meddelanden etc.
  • Referens
    De flesta aviseringar kommer att ha en referens till en post i din databas eller ditt program.

Varje system jag har arbetat med hade en enkel 1 till 1 referensförhållande på en avisering kan du ha ett 1 till n kom ihåg att jag kommer att fortsätta mitt exempel med 1:1. Detta betyder också att jag inte behöver ett fält som definierar vilken typ av objekt som refereras till eftersom detta definieras av meddelandetypen.

SQL-tabell

När vi nu definierar en riktig tabellstruktur för SQL kommer vi till några beslut när det gäller databasdesignen. Jag kommer att välja den enklaste lösningen som kommer att se ut ungefär så här:

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

Eller för de lata människorna SQL create table-kommandot för det här exemplet:

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-tjänst

Denna implementering beror helt på behoven för din applikation, Obs! Detta är ett exempel som inte är den gyllene standarden på hur man bygger ett meddelandesystem i PHP.

Aviseringsmodell

Detta är ett exempel på en basmodell av själva meddelandet, inget fancy bara de nödvändiga egenskaperna och de abstrakta metoderna messageForNotification och messageForNotifications vi förväntade oss att implementeras i de olika aviseringstyperna.

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

Du måste lägga till en konstruktör , getters , sättare och den typen av saker själv i din egen stil, jag tänker inte tillhandahålla ett aviseringssystem som är klart att använda.

Meddelandetyper

Nu kan du skapa en ny Notification underklass för varje typ. Följande exempel skulle hantera gilla-åtgärden av en kommentar:

  • Ray har gillat din kommentar. (1 avisering)
  • John och Jane gillade din kommentar. (2 aviseringar)
  • Jane, Johnny, James och Jenny gillade din kommentar. (4 aviseringar)
  • Jonny, James och 12 andra gillade din kommentar. (14 aviseringar)

Exempelimplementering:

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

Meddelandehanterare

För att arbeta med dina aviseringar i din applikation, skapa något som en aviseringshanterare:

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 egenskapen bör innehålla logiken i direkt kommunikation med din databackend i fallet med det här exemplet mysql.

Skapa aviseringar

Använder mysql triggers är inte fel, för det finns ingen fel lösning. Det som fungerar, fungerar... Men jag rekommenderar starkt att inte låta databasen hantera applikationslogik.

Så i aviseringshanteraren kanske du vill göra något så här:

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

Bakom add metod för notificationAdapter kan vara ett rå mysql insert-kommando. Genom att använda denna adapterabstraktion kan du enkelt byta från mysql till en dokumentbaserad databas som mongodb vilket skulle vara vettigt för ett meddelandesystem.

isDoublicate metod på notificationAdapter bör helt enkelt kontrollera om det redan finns ett meddelande med samma recipient , sender , type och reference .

Jag kan inte nog påpeka att detta bara är ett exempel. (Också jag måste verkligen förkorta nästa steg detta inlägg börjar bli löjligt långt -.-)

Så om du antar att du har någon form av kontroller med en åtgärd när en lärare laddar upp läxor:

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

Kommer att skapa ett meddelande för varje lärares elev när han laddar upp en ny läxa.

Läser aviseringarna

Nu kommer den svåra delen. Problemet med att gruppera på PHP-sidan är att du måste ladda alla meddelanden från den aktuella användaren för att gruppera dem korrekt. Detta skulle vara dåligt, ja om du bara har ett fåtal användare skulle det förmodligen fortfarande inte vara några problem, men det gör det inte bra.

Den enkla lösningen är att helt enkelt begränsa antalet begärda meddelanden och bara gruppera dessa. Detta kommer att fungera bra när det inte finns många liknande meddelanden (som 3-4 per 20). Men låt oss säga att inlägget från en användare/student får ungefär hundra likes och att du bara väljer de senaste 20 aviseringarna. Användaren kommer då bara att se att 20 personer gillade hans inlägg, vilket också skulle vara hans enda meddelande.

En "korrekt" lösning skulle vara att gruppera meddelanden som redan finns i databasen och bara välja några exempel per meddelandegrupp. Då skulle du bara behöva injicera den verkliga räkningen i dina aviseringsmeddelanden.

Du har förmodligen inte läst texten nedan så låt mig fortsätta med ett stycke:

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

Nu vet du vilka aviseringar som ska finnas för den givna användaren och hur många aviseringar gruppen innehåller.

Och nu den taskiga delen. Jag kunde fortfarande inte hitta ett bättre sätt att välja ett begränsat antal aviseringar för varje grupp utan att göra en fråga för varje grupp. Alla förslag här är mycket välkomna.

Så jag gör något i stil med:

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

Jag kommer nu att fortsätta att anta att notificationAdapter s get metoden implementerar denna gruppering och returnerar en array så här:

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

Eftersom vi alltid har minst en avisering i vår grupp och vår beställning föredrar Oläst och Ny aviseringar kan vi bara använda den första aviseringen som ett exempel för rendering.

Så för att kunna arbeta med dessa grupperade meddelanden behöver vi ett nytt 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);
    }
}

Och äntligen kan vi faktiskt sätta ihop det mesta. Så här får funktionen på NotificationManager kan se ut så här:

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

Och äntligen inuti en möjlig kontrollåtgärd:

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. Flera främmande nycklar i en tabell till en annan tabell i mysql

  2. MySQL 'skapa schema' och 'skapa databas' - Finns det någon skillnad

  3. Hur implicita transaktioner fungerar i SQL Server

  4. Hämta rader grupperade efter timme med MySQL