Ответ 1
Хорошо, что этот вопрос составляет 9 месяцев, поэтому я не уверен, что OP все еще нуждается в ответе, но из-за многих просмотров и вкусной щедрости я хотел бы также добавить свою горчицу (немецкая поговорка..).
В этом посте я попытаюсь сделать простой объясненный пример о том, как начать создание системы уведомлений.
Изменить: Хорошо, это получилось так, путь, путь дольше, чем я ожидал. Я очень устал, в конце концов, извините.
WTL;DR;
Вопрос 1: имеет флаг для каждого уведомления.
Вопрос 2: Сохраняйте каждое уведомление как отдельную запись в своей базе данных и группируйте их, когда они запрашиваются.
Структура
Я предполагаю, что уведомления будут выглядеть примерно так:
+---------------------------------------------+
| ▣ James has uploaded new Homework: Math 1+1 |
+---------------------------------------------+
| ▣ Jane and John liked your comment: Im s... |
+---------------------------------------------+
| ▢ The School is closed on independence day. |
+---------------------------------------------+
За шторами это может выглядеть примерно так:
+--------+-----------+--------+-----------------+-------------------------------------------+
| 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. |
+--------+-----------+--------+-----------------+-------------------------------------------+
Примечание. Я не рекомендую группировать уведомления внутри базы данных, делать это во время выполнения, это держит вещи намного более гибкими.
- непрочитанная
Каждое уведомление должно иметь флаг, указывающий, уже получил ли получатель уведомление. - Получатель
Определяет, кто получает уведомление. - Sender
Определяет, кто вызвал уведомление. - Тип
Вместо того, чтобы каждое сообщение в виде обычного текста внутри вашей базы данных создавало типы. Таким образом, вы можете создавать специальные обработчики для разных типов уведомлений внутри вашего бэкэнда. Уменьшит объем данных, хранящихся в вашей базе данных, и даст вам еще большую гибкость, позволит легко переводить уведомления, изменения прошлых сообщений и т.д. - Ссылка
Большинство уведомлений будут иметь ссылку на запись в вашей базе данных или в вашем приложении.
Каждая система, над которой я работал, имела простую ссылку от 1 до 1 в уведомлении, у вас может быть от 1 до n, помните, что я продолжу мой пример с 1:1. Это также означает, что мне не нужно поле, определяющее тип ссылки на объект, потому что это определяется типом уведомления.
Таблица SQL
Теперь при определении реальной структуры таблицы для SQL мы принимаем несколько решений в отношении дизайна базы данных. Я пойду с простейшим решением, которое будет выглядеть примерно так:
+--------------+--------+---------------------------------------------------------+
| column | type | description |
+--------------+--------+---------------------------------------------------------+
| id | int | Primary key |
+--------------+--------+---------------------------------------------------------+
| recipient_id | int | The receivers user id. |
+--------------+--------+---------------------------------------------------------+
| sender_id | int | The sender 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. |
+--------------+--------+---------------------------------------------------------+
Или для ленивых людей SQL create table для этого примера:
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
Эта реализация полностью зависит от потребностей вашего приложения, Примечание.. Это пример не золотой стандарт создания системы уведомлений в PHP.
Модель уведомления
Это примерная базовая модель самого уведомления, ничто не представляет собой только нужные свойства и абстрактные методы messageForNotification
и messageForNotifications
, которые мы ожидали реализовать в разных типах уведомлений.
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);
}
}
Вам нужно будет добавить конструктор, геттеры, сеттеры и такие вещи сами по себе в вашем собственном стиле, я не собираюсь предоставлять готовые к использованию системы уведомлений.
Типы уведомлений
Теперь вы можете создать новый подкласс Notification
для каждого типа. В следующем примере будет обрабатываться подобное действие комментария:
- Рэй понравился ваш комментарий. (1 уведомление)
- Джон и Джейн понравились ваши комментарии. (2 уведомления)
- Джейн, Джонни, Джеймс и Дженни понравились ваши комментарии. (4 уведомления)
- Jonny, James и еще 12 понравились ваши комментарии. (14 уведомлений)
Пример реализации:
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
}
}
Менеджер уведомлений
Чтобы работать с вашими уведомлениями внутри вашего приложения, создайте что-то вроде диспетчера уведомлений:
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
должно содержать логику в прямой связи с вашей базой данных в случае этого примера mysql.
Создание уведомлений
Использование mysql
триггеров не является неправильным, потому что нет неправильного решения. Что работает, работает. Но я настоятельно рекомендую не позволять базе данных обрабатывать логику приложения.
Итак, внутри диспетчера уведомлений вы можете сделать что-то вроде этого:
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(),
]);
}
}
За add
метод notificationAdapter
может быть исходной командой вставки mysql. Использование абстракции этого адаптера позволяет легко переключиться с mysql на базу данных на основе документа, такую как mongodb, которая будет иметь смысл для системы уведомлений.
Метод isDoublicate
на notificationAdapter
должен просто проверить, есть ли уже уведомление с теми же recipient
, sender
, type
и reference
.
Я не могу указать достаточно, что это только пример. (Также мне действительно нужно сократить следующие шаги, которые этот пост вызывает смехотворно долго.)
Итак, если у вас есть какой-то контроллер с действием, когда учитель загружает домашнюю работу:
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);
}
}
Создает уведомление для каждого ученика-учителя, когда он загружает новую домашнюю работу.
Чтение уведомлений
Теперь идет сложная часть. Проблема с группировкой на стороне PHP заключается в том, что вам придется загружать уведомления all текущего пользователя, чтобы их правильно группировать. Это было бы плохо, если бы у вас было всего несколько пользователей, это, вероятно, все равно было бы проблемой, но это не делает это хорошо.
Простое решение - просто ограничить количество запрошенных уведомлений и только группировать их. Это будет хорошо работать, когда не так много похожих уведомлений (например, 3-4 на 20). Но скажем, что пост пользователя/ученика получает около сотни симпатий, и вы выбираете только 20 последних уведомлений. Затем пользователь увидит, что 20 человек тоже понравились ему, и это будет его единственным уведомлением.
"Правильное" решение будет группировать уведомления уже в базе данных и выбирать только некоторые образцы в группе уведомлений. Вместо того, чтобы просто вводить реальный счет в свои уведомления.
Вероятно, вы не читали текст ниже, поэтому позвольте мне продолжить фрагмент:
select *, count(*) as count from notifications
where recipient_id = 1
group by `type`, `reference_id`
order by created_at desc, unread desc
limit 20
Теперь вы знаете, какие уведомления должны быть для данного пользователя и сколько уведомлений содержит группа.
А теперь дерьмовая часть. Я все еще не мог найти лучший способ выбрать ограниченное количество уведомлений для каждой группы, не делая запрос для каждой группы. Все предложения здесь очень приветствуются.
Итак, я делаю что-то вроде:
$notifcationGroups = [];
foreach($results as $notification)
{
$notifcationGroup = ['count' => $notification['count']];
// when the group only contains one item we don't
// have to select it 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;
}
Теперь я буду продолжать считать, что метод notificationAdapter
get
реализует эту группировку и возвращает такой массив:
[
{
count: 12,
items: [Note1, Note2, Note3, Note4, Note5]
},
{
count: 1,
items: [Note1]
},
{
count: 3,
items: [Note1, Note2, Note3]
}
]
Поскольку у нас всегда есть как минимум одно уведомление в нашей группе, и наш заказ предпочитает Непрочитанные и Новые уведомления, мы можем просто использовать первое уведомление в качестве образца для рендеринга.
Чтобы иметь возможность работать с этими групповыми уведомлениями, нам нужен новый объект:
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);
}
}
И, наконец, мы можем собрать большую часть материала. Вот как выглядит функция get на NotificationManager
:
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;
}
И действительно, наконец, внутри возможного действия контроллера:
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);
}