Nou, deze vraag is 9 maanden oud, dus ik weet niet zeker of OP nog steeds een antwoord nodig heeft, maar vanwege de vele views en de smakelijke premie zou ik ook mijn mosterd willen toevoegen (Duits gezegde..).
In dit bericht zal ik proberen een eenvoudig uitgelegd voorbeeld te maken over hoe u kunt beginnen met het bouwen van een meldingssysteem.
Bewerken: Nou ok dit bleek veel, veel, veel langer dan ik had verwacht. Ik werd uiteindelijk erg moe, het spijt me.
WTLDR;
Vraag 1: hebben een vlag op elke melding.
Vraag 2: Bewaar nog steeds elke melding als een enkel record in uw database en groepeer ze wanneer ze worden gevraagd.
Structuur
Ik neem aan dat de meldingen er ongeveer zo uit zullen zien:
+---------------------------------------------+
| ▣ James has uploaded new Homework: Math 1+1 |
+---------------------------------------------+
| ▣ Jane and John liked your comment: Im s... |
+---------------------------------------------+
| ▢ The School is closed on independence day. |
+---------------------------------------------+
Achter de gordijnen zou dit er ongeveer zo uit kunnen zien:
+--------+-----------+--------+-----------------+-------------------------------------------+
| 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. |
+--------+-----------+--------+-----------------+-------------------------------------------+
Opmerking: Ik raad niet aan om de meldingen in de database te groeperen, doe dat tijdens runtime, dit houdt de zaken een stuk flexibeler.
- Ongelezen
Elke melding moet een vlag hebben om aan te geven of de ontvanger de melding al heeft geopend. - Ontvanger
Definieert wie de melding ontvangt. - Afzender
Definieert wie de melding heeft geactiveerd. - Type
In plaats van elk bericht in platte tekst in uw database te hebben, maakt u typen. Op deze manier kunt u speciale handlers maken voor verschillende meldingstypen in uw backend. Vermindert de hoeveelheid gegevens die in uw database is opgeslagen en geeft u nog meer flexibiliteit, waardoor u eenvoudig meldingen, wijzigingen van eerdere berichten enz. kunt vertalen. - Referentie
De meeste meldingen hebben een verwijzing naar een record in uw database of uw toepassing.
Elk systeem waaraan ik heb gewerkt had een simpele 1 op 1 referentierelatie op een melding, heeft u mogelijk een 1 tot n onthoud dat ik mijn voorbeeld met 1:1 voortzet. Dit betekent ook dat ik geen veld nodig heb dat bepaalt naar welk type object wordt verwezen, omdat dit wordt gedefinieerd door het meldingstype.
SQL-tabel
Als we nu een echte tabelstructuur voor SQL definiëren, komen we tot een paar beslissingen met betrekking tot het databaseontwerp. Ik ga voor de eenvoudigste oplossing die er ongeveer zo uitziet:
+--------------+--------+---------------------------------------------------------+
| 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. |
+--------------+--------+---------------------------------------------------------+
Of voor de luie mensen de opdracht SQL create table voor dit voorbeeld:
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-service
Deze implementatie hangt volledig af van de behoeften van uw toepassing, Opmerking: Dit is een voorbeeld en niet de gouden standaard voor het bouwen van een meldingssysteem in PHP.
Meldingsmodel
Dit is een voorbeeldbasismodel van de melding zelf, niets bijzonders, alleen de benodigde eigenschappen en de abstracte methoden messageForNotification
en messageForNotifications
we verwachtten dat het zou worden geïmplementeerd in de verschillende soorten meldingen.
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);
}
}
U moet een constructor toevoegen , getters , zetters en dat soort dingen alleen in je eigen stijl, ik ga geen kant-en-klaar meldingssysteem leveren.
Meldingstypen
Nu kunt u een nieuwe Notification
maken subklasse voor elk type. Dit volgende voorbeeld zou de like-actie behandelen van een opmerking:
- Ray heeft je reactie leuk gevonden. (1 melding)
- John en Jane vonden je reactie leuk. (2 meldingen)
- Jane, Johnny, James en Jenny vonden je reactie leuk. (4 meldingen)
- Jonny, James en 12 anderen vonden je reactie leuk. (14 meldingen)
Voorbeeld implementatie:
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
}
}
Meldingsmanager
Om met uw meldingen in uw applicatie te werken, maakt u zoiets als een meldingsmanager:
class NotificationManager
{
protected $notificationAdapter;
public function add(Notification $notification);
public function markRead(array $notifications);
public function get(User $user, $limit = 20, $offset = 0) : array;
}
De notificationAdapter
eigenschap moet de logica bevatten in directe communicatie met uw data-backend in het geval van dit voorbeeld mysql.
Meldingen maken
mysql
gebruiken triggers is niet verkeerd, want er is geen verkeerde oplossing. Wat werkt, werkt.. Maar ik raad ten zeerste aan om de database de applicatielogica niet te laten verwerken.
Dus in de notificatiemanager zou je zoiets als dit kunnen doen:
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(),
]);
}
}
Achter de add
methode van de notificationAdapter
kan een onbewerkte mysql-invoegopdracht zijn. Door deze adapterabstractie te gebruiken, kunt u eenvoudig overschakelen van mysql naar een op documenten gebaseerde database zoals mongodb wat logisch zou zijn voor een meldingssysteem.
De isDoublicate
methode op de notificationAdapter
moet gewoon controleren of er al een melding is met dezelfde recipient
, sender
, type
en reference
.
Ik kan er niet genoeg op wijzen dat dit slechts een voorbeeld is. (Ook moet ik de volgende stappen echt inkorten, dit bericht wordt belachelijk lang -.-)
Dus aangenomen dat je een soort controller hebt met een actie wanneer een leraar huiswerk uploadt:
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);
}
}
Zal een melding maken voor de leerling van elke leraar wanneer hij een nieuw huiswerk uploadt.
De meldingen lezen
Nu komt het moeilijkste. Het probleem met groeperen aan de PHP-kant is dat je alle . moet laden meldingen van de huidige gebruiker om ze correct te groeperen. Dit zou slecht zijn, nou als je maar een paar gebruikers hebt, zou het waarschijnlijk nog steeds geen probleem zijn, maar dat maakt het nog niet goed.
De makkelijke oplossing is om simpelweg het aantal aangevraagde meldingen te beperken en deze alleen te groeperen. Dit werkt prima als er niet veel vergelijkbare meldingen zijn (zoals 3-4 per 20). Maar stel dat de post van een gebruiker/student zo'n honderd likes krijgt en je selecteert alleen de laatste 20 notificaties. De gebruiker zal dan alleen zien dat 20 mensen zijn bericht leuk vonden, ook dat zou zijn enige melding zijn.
Een "juiste" oplossing zou zijn om de meldingen die al in de database staan te groeperen en slechts enkele voorbeelden per meldingsgroep te selecteren. Dan zou je gewoon de echte telling in je meldingsberichten moeten injecteren.
Je hebt de onderstaande tekst waarschijnlijk niet gelezen, dus laat me doorgaan met een fragment:
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 weet je welke meldingen er voor de betreffende gebruiker moeten zijn en hoeveel meldingen de groep bevat.
En nu het stomme gedeelte. Ik kon nog steeds geen betere manier vinden om een beperkt aantal meldingen voor elke groep te selecteren zonder voor elke groep een zoekopdracht uit te voeren. Alle suggesties hier zijn van harte welkom.
Dus ik doe iets als:
$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;
}
Ik ga er nu vanuit dat de notificationAdapter
s get
methode implementeert deze groepering en retourneert een array zoals deze:
[
{
count: 12,
items: [Note1, Note2, Note3, Note4, Note5]
},
{
count: 1,
items: [Note1]
},
{
count: 3,
items: [Note1, Note2, Note3]
}
]
Omdat we altijd ten minste één melding in onze groep hebben en onze bestelling de voorkeur geeft Ongelezen en Nieuw meldingen kunnen we de eerste melding gewoon gebruiken als voorbeeld voor weergave.
Dus om met deze gegroepeerde meldingen te kunnen werken, hebben we een nieuw object nodig:
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);
}
}
En eindelijk kunnen we de meeste dingen echt samenvoegen. Dit is hoe de get-functie op de NotificationManager
zou er als volgt uit kunnen zien:
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;
}
En echt eindelijk binnen een mogelijke controller-actie:
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);
}