Saltar al contenido

Sistema de notificaciones usando php y mysql

Solución:

Bueno, esta pregunta tiene 9 meses, así que no estoy seguro de si OP todavía necesita una respuesta, pero debido a las muchas visitas y la sabrosa recompensa, me gustaría agregar también mi mostaza (refrán alemán …).

En esta publicación intentaré hacer un ejemplo explicado simple sobre cómo comenzar a construir un sistema de notificación.

Editar: Bueno, esto resultó mucho, mucho, mucho más largo de lo que esperaba. Me cansé mucho al final, lo siento.

WTLDR;

Pregunta 1: tener una bandera en cada notificación.

Pregunta 2: Aún almacene cada notificación como un registro único dentro de su base de datos y agrúpelas cuando se soliciten.


Estructura

Supongo que las notificaciones se verán así:

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

Detrás de las cortinas, esto podría verse así:

+--------+-----------+--------+-----------------+-------------------------------------------+
| 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: No recomiendo agrupar las notificaciones dentro de la base de datos, hazlo en tiempo de ejecución, esto mantiene las cosas mucho más flexibles.

  • No leído

    Cada notificación debe tener una bandera para indicar si el destinatario ya ha abierto la notificación.

  • Recipiente

    Define quién recibe la notificación.

  • Remitente

    Define quién desencadenó la notificación.

  • Escribe

    En lugar de tener todos los mensajes en texto plano dentro de su base de datos, cree tipos. De esta manera, puede crear controladores especiales para diferentes tipos de notificaciones dentro de su backend. Reducirá la cantidad de datos almacenados dentro de su base de datos y le dará aún más flexibilidad, posibilitó la traducción fácil de notificaciones, cambios de mensajes pasados, etc.

  • Referencia

    La mayoría de las notificaciones tendrán una referencia a un registro en su base de datos o su aplicación.

Cada sistema en el que he estado trabajando tenía un simple 1 a 1 relación de referencia en una notificación, es posible que tenga una 1 an tenga en cuenta que continuaré mi ejemplo con 1: 1. Esto también significa que no necesito un campo que defina a qué tipo de objeto se hace referencia porque esto está definido por el tipo de notificación.

Tabla SQL

Ahora, al definir una estructura de tabla real para SQL, llegamos a algunas decisiones en términos del diseño de la base de datos. Iré con la solución más simple que se verá así:

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

O para los perezosos el Comando SQL crear tabla para este ejemplo:

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;

Servicio PHP

Esta implementación depende completamente de las necesidades de su aplicación, Nota: Este es un ejemplo, no el estándar de oro sobre cómo construir un sistema de notificación en PHP.

Modelo de notificación

Este es un modelo base de ejemplo de la notificación en sí, nada lujoso, solo las propiedades necesarias y los métodos abstractos messageForNotification y messageForNotifications esperábamos ser implementado en los diferentes tipos de notificación.

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

Tendrás que agregar un constructor, captadores, setters y ese tipo de cosas por ti mismo en tu propio estilo, no voy a proporcionar un sistema de notificación listo para usar.

Tipos de notificación

Ahora puedes crear un nuevo Notification subclase para cada tipo. Este siguiente ejemplo manejaría el como acción de un comentario:

  • A Ray le ha gustado tu comentario. (1 notificación)
  • A John y Jane les gustó tu comentario. (2 notificaciones)
  • A Jane, Johnny, James y Jenny les gustó tu comentario. (4 notificaciones)
  • A Jonny, James y otras 12 personas les gustó tu comentario. (14 notificaciones)

Implementación de ejemplo:

namespace NotificationComment;

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

Gerente de notificaciones

Para trabajar con sus notificaciones dentro de su aplicación, cree algo como un administrador de notificaciones:

class NotificationManager
{
    protected $notificationAdapter;

    public function add(Notification $notification);

    public function markRead(array $notifications);

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

los notificationAdapter La propiedad debe contener la lógica en comunicación directa con su backend de datos en el caso de este ejemplo mysql.

Creando notificaciones

Utilizando mysql triggers no está mal, porque no hay una solución incorrecta. Lo que funciona, funciona … Pero recomiendo encarecidamente no dejar que la base de datos maneje la lógica de la aplicación.

Entonces, dentro del administrador de notificaciones, es posible que desee hacer algo como esto:

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

Detrás de la add método del notificationAdapter puede ser un comando de inserción de mysql sin formato. El uso de esta abstracción de adaptador le permite cambiar fácilmente de mysql a una base de datos basada en documentos como mongodb lo que tendría sentido para un sistema de notificación.

los isDoublicate método en el notificationAdapter simplemente debe verificar si ya hay una notificación con el mismo recipient, sender, type y reference.


No puedo señalar lo suficiente que esto es solo un ejemplo. (También tengo que acortar los próximos pasos que esta publicación se está volviendo ridículamente larga -.-)


Entonces, suponiendo que tenga algún tipo de controlador con una acción cuando un maestro carga la tarea:

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 NotificationHomeworkHomeworkUploadedNotification;
        $notification->sender = $homework->teacher;
        $notification->recipient = $student;
        $notification->reference = $homework;

        // send the notification
        $notificationManager->add($notification);
    }
}

Creará una notificación para el alumno de cada maestro cuando cargue una nueva tarea.

Leer las notificaciones

Ahora viene la parte difícil. El problema con la agrupación en el lado de PHP es que tendrá que cargar todos notificaciones del usuario actual para agruparlas correctamente. Esto sería malo, bueno, si solo tiene unos pocos usuarios, probablemente aún no sea un problema, pero eso no lo hace bueno.

La solución fácil es limitar simplemente el número de notificaciones solicitadas y agruparlas únicamente. Esto funcionará bien cuando no haya muchas notificaciones similares (como 3-4 por 20). Pero digamos que la publicación de un usuario / estudiante obtiene alrededor de cien me gusta y solo selecciona las últimas 20 notificaciones. El usuario solo verá que a 20 personas les gustó su publicación y esa sería su única notificación.

Una solución “correcta” sería agrupar las notificaciones que ya están en la base de datos y seleccionar solo algunas muestras por grupo de notificación. Entonces solo tendría que inyectar el recuento real en sus mensajes de notificación.

Probablemente no leyó el texto a continuación, así que permítame continuar con un fragmento:

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

Ahora sabe qué notificaciones deben estar disponibles para el usuario determinado y cuántas notificaciones contiene el grupo.

Y ahora la parte de mierda. Todavía no pude encontrar una mejor manera de seleccionar un número limitado de notificaciones para cada grupo sin hacer una consulta para cada grupo. Todas las sugerencias aquí son bienvenidas.

Entonces hago algo como:

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

Continuaré ahora asumiendo que el notificationAdapters get El método implementa esta agrupación y devuelve una matriz como esta:

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

Porque siempre tenemos al menos una notificación en nuestro grupo y nuestro pedido prefiere No leído y Nuevo notificaciones, podemos usar la primera notificación como muestra para renderizar.

Entonces, para poder trabajar con estas notificaciones agrupadas, necesitamos un nuevo objeto:

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

Y finalmente podemos juntar la mayoría de las cosas. Así es como la función get en el NotificationManager podría verse así:

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

Y realmente, finalmente, dentro de una posible acción del controlador:

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

Respuesta:

  1. Introduzca una variable leída / no leída en la notificación. A continuación, puede extraer solo las notificaciones no leídas haciendo … WHERE status = “UNREAD” en su sql.

  2. Realmente no puedes … querrás enviar esa notificación. Sin embargo, lo que puede hacer es agregarlos utilizando GROUP BY. Es probable que desee agrupar en algo único como una nueva tarea, por lo que podría ser algo como … homework.id

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *