Уведомления

Система уведомлений Imhotep предоставляет унифицированный интерфейс для отправки сообщений пользователям через различные каналы связи. Компонент спроектирован с учетом расширяемости и гибкости, позволяя легко добавлять новые способы доставки сообщений.

Конфигурирование

Система уведомлений Imhotep уже включена в ядро приложения. Для начала работы необходимо настроить каналы доставки в файле конфигурации config/notifications.php.

Система поддерживает следующие драйверы:

Пример минимальной конфигурации:

return [
    'default' => 'mail',
    
    'channels' => [
        'mail' => [
            'driver' => 'mail',
            'server' => 'smtp.example.com',
            'port' => 587,
            'login' => 'user@example.com',
            'password' => 'password',
        ],
        
        'database' => [
            'driver' => 'database',
            'table' => 'notifications',
        ],
        
        'telegram' => [
            'driver' => 'telegram',
            'token' => 'YOUR_BOT_TOKEN',
        ],
        
        'fcm' => [
            'driver' => 'fcm',
            'project_id' => 'your-project-id',
            'credentials_path' => '/path/to/credentials.json',
        ],
    ],
];
PHP

Каждый драйвер поддерживает дополнительные параметры конфигурации, которые можно найти в соответствующей документации.

Создание уведомлений

В Imhotep каждое уведомление представлено отдельным классом, расширяющим базовый класс Imhotep\Notifications\Notification. Для быстрого создания уведомления используйте консольную комманду make:notification, которая поместит новый класс уведомления в каталог app/Notifications:

php imhotep make:notification OrderShipped
BASH

Пример класса уведомления

<?php

namespace App\Notifications;

use Imhotep\Notifications\Notification;
use Imhotep\Notifications\Messages\MailMessage;
use Imhotep\Notifications\Messages\DatabaseMessage;

class OrderShipped extends Notification
{
    protected $order;
    
    public function __construct($order)
    {
        $this->order = $order; 
    }
    
    // Определение каналов доставки
    // Уведомление будет отправлено на почту и в базу данных
    public function via($recipient): array
    {
        return ['mail', 'database'];
    }
    
    // Формирование почтового уведомления
    public function toMail($recipient): MailMessage
    {
        return (new MailMessage())
            ->subject('Заказ #'.$this->order->id.' отправлен')
            ->line('Ваш заказ был успешно отправлен.')
            ->line('Трек-номер: '.$this->order->tracking_number)
            ->action('Отследить заказ', url('/orders/'.$this->order->id))
            ->priority(1); // Высокий приоритет
    }
    
    // Формирование уведомления для сохранения в базе данных
    public function toDatabase($recipient): DatabaseMessage
    {
        return new DatabaseMessage([
            'order_id' => $this->order->id,
            'status' => 'shipped',
            'tracking_number' => $this->order->tracking_number,
        ]);
    }
}
PHP

Отправка уведомлений

Определение каналов доставки

Каждый класс уведомления содержит метод via(), который возвращает один или несколько каналов доставки. По умолчанию система поддерживает следующие предустановленные каналы: mail, database, telegram и fcm (Firebase Cloud Messaging). При необходимости вы можете добавить собственные каналы доставки уведомлений.

Метод via() принимает первым параметром $recipient — экземпляр класса, которому отправляется уведомление, или строковое значение (например, email-адрес получателя). Используя $recipient, вы можете динамически определять каналы доставки:

public function via(mixed $recipient): array
{
    // Всегда сохраняем в базу данных
    $channels = ['database'];
    
    if ($recipient instanceof User) {
        // Добавляем email, если пользователь включил email-уведомления
        if ($recipient->email_notifications) {
            $channels[] = 'mail';
        }
        
        // Добавляем Telegram, если указан chat_id
        if ($recipient->telegram_chat_id) {
            $channels[] = 'telegram';
        }
    }
    
    // Если получатель — email-адрес (строка), отправляем только по почте
    if (is_string($recipient)) {
        return ['mail'];
    }
    
    return $channels;
}
PHP

Метод via() вызывается для каждого получателя отдельно, что позволяет настраивать каналы доставки индивидуально для каждого пользователя.

Подтверждение отправки уведомления

Иногда возникают ситуации, когда уведомление не должно быть доставлено по внешним причинам. Например:

  • Отправка SMS только в дневное время
  • Уведомление задержалось в очереди, и информация уже устарела
  • Пользователь отключил определённый канал получения уведомлений
  • И другие подобные ситуации

Для таких случаев вы можете определить метод shouldSend, который вызывается непосредственно перед отправкой уведомления. Если метод возвращает false, уведомление не будет отправлено через указанный канал.

public function shouldSend(User $recipient, string $channel): bool
{
    // Если заказ уже был отменён, не отправлять уведомление
    if ($this->order->status === 'cancelled') {
        return false;
    }

    // Не отправлять SMS ночью
    if ($channel === 'sms' && date('H') < 8) {
        return false;
    }
    
    // Проверяем настройки пользователя
    if ($channel === 'telegram' && !$recipient->telegram_notifications) {
        return false;
    }
    
    return true;
}
PHP

Важно: Метод shouldSend вызывается для каждого канала отдельно, что позволяет гибко управлять отправкой через разные каналы связи.

Язык уведомлений

Imhotep позволяет отправлять уведомления на языке пользователя, а не системы. Для этого, класс Imhotep\Notifications\Notification содержит метод locale для установки желаемого языка.

// Установка языка уведомления для конкретного пользователя
$user->notify((new OrderShipped($order))->locale('zh'));

// Установка языка уведомлений для нескольких пользвателей через фасад
Notification::locale('zh')->send($users, new OrderShipped($order));
PHP

Автоматическое определение языка пользователя:

Если в вашем приложении пользователи могут настраивать предпочитаемый язык индивидуально, тогда уведомляемая модель должна реализовывать интерфейс HasLocalePreference. Это позволит Imhotep автоматически использовать указанный язык при отправке уведомлений без явного вызова метода locale().

use Imhotep\Contracts\Localization\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * Получить предпочитаемый пользователем язык.
     */
    public function preferredLocale(): string
    {
        return $this->locale; // Поле 'locale' в таблице пользователей
    }
}
PHP

Если язык уведомления не был установлен явно, будет использован язык из app()->getLocale(). Язык, установленный через метод locale(), имеет приоритет над значением из preferredLocale().

События

Система уведомлений Imhotep предоставляет несколько событий для полного контроля над жизненным циклом уведомлений. Позволяя создавать надежные системы коммуникации с подробным логированием и обработкой ошибок.

Перед отправкой NotificationSending

Событие Imhotep\Notifications\Events\NotificationSending позволяет выполнить проверки или модификации перед фактической отправкой. Уведомление не будет отправлено, если слушатель события вернет false в методе handle():

use Imhotep\Notifications\Events\NotificationSending;

class ValidateNotification
{
    public function handle(NotificationSending $event): bool|null
    {
        $channel = $event->channel; // Канал отправки (mail, database и т.д.)
        $recipient = $event->recipient; // Получатель уведомления
        $notification = $event->notification; // Экземпляр уведомления
        
        // Пример: проверка времени отправки
        if ($channel === 'sms' && date('H') < 8) {
            return false; // Отменяем отправку SMS ночью
        }
        
        return true; // Разрешаем отправку
    }
}
PHP

После отправки NotificationSent

После успешной отправки уведомления система запускает событие Imhotep\Notifications\Events\NotificationSent. Это позволяет выполнить логирование, обновление статистики или другие постобработки.

use Imhotep\Notifications\Events\NotificationSent;

class LogNotification
{
    public function handle(NotificationSent $event): void
    {
        $channel = $event->channel; // Канал отправки
        $recipient = $event->recipient; // Получатель
        $notification = $event->notification; // Уведомление
        $response = $event->response; // Ответ сервиса доставки
        
        // Логируем отправку
        Log::info("Уведомление отправлено", [
            'channel' => $channel,
            'recipient' => $recipient->id,
            'notification' => get_class($notification),
            'response' => $response,
        ]);
        
        // Обновляем статистику
        Cache::increment('notifications_count');
    }
}
PHP

Пропуск отправки NotificationSkipped

Событие Imhotep\Notifications\Events\NotificationSkipped вызывается, когда уведомление было пропущено (например, метод shouldSend() вернул false или слушатель NotificationSending вернул false).

use Imhotep\Notifications\Events\NotificationSkipped;

class LogSkippedNotifications
{
    public function handle(NotificationSkipped $event): void
    {
        Log::info('Уведомление пропущено', [
            'recipient' => $event->recipient->id,
            'notification' => get_class($event->notification),
            'channel' => $event->channel,
        ]);
    }
}
PHP

Неудачная отправка NotificationFailed

Событие Imhotep\Notifications\Events\NotificationFailed вызывается при возникновении ошибки во время отправки уведомления.

use Imhotep\Notifications\Events\NotificationFailed;

class LogNotificationFailure
{
    public function handle(NotificationFailed $event): void
    {
        // Логируем ошибку
        Log::error('Ошибка отправки уведомления', [
            'recipient' => $event->recipient->id,
            'notification' => get_class($event->notification),
            'channel' => $event->channel,
            'error' => $event->exception->getMessage(),
            'trace' => $event->exception->getTraceAsString(),
        ]);
    }
}
PHP