Перейти к содержимому

Notifly в Drupal

В Drupal удобнее всего сделать собственный custom-модуль notifly_alerts — один файл и info.yml. Никаких composer-зависимостей не требуется, используется встроенный \Drupal::httpClient().

modules/custom/notifly_alerts/
├── notifly_alerts.info.yml
├── notifly_alerts.module
└── src/
└── Notifly.php

notifly_alerts.info.yml:

name: 'Notifly Alerts'
type: module
description: 'Push-уведомления через сервер Notifly.'
core_version_requirement: ^9 || ^10
package: Custom

src/Notifly.php:

<?php
namespace Drupal\notifly_alerts;
class Notifly {
public static function send(string $title, string $message, int $priority = 5): void {
$config = \Drupal::config('notifly_alerts.settings');
$url = $config->get('url') ?: getenv('NOTIFLY_URL');
$token = $config->get('token') ?: getenv('NOTIFLY_TOKEN');
if (!$url || !$token) {
return;
}
try {
\Drupal::httpClient()->post("$url/message?token=$token", [
'json' => [
'title' => mb_substr($title, 0, 200),
'message' => mb_substr($message, 0, 1500),
'priority' => $priority,
],
'timeout' => 5,
]);
} catch (\Throwable $e) {
// Никогда не ломаем основной запрос Drupal из-за нотификации.
}
}
}

notifly_alerts.module:

<?php
use Drupal\notifly_alerts\Notifly;
use Drupal\node\NodeInterface;
use Drupal\user\UserInterface;
use Drupal\Core\Logger\RfcLogLevel;
/**
* Новый узел опубликован.
*/
function notifly_alerts_entity_insert($entity) {
if ($entity instanceof NodeInterface) {
Notifly::send(
"📝 Новый узел: {$entity->getTitle()}",
"Тип: {$entity->bundle()}\n" .
"Автор: {$entity->getOwner()->getAccountName()}\n" .
"URL: " . $entity->toUrl('canonical', ['absolute' => TRUE])->toString(),
4
);
}
}
/**
* Регистрация пользователя.
*/
function notifly_alerts_user_insert(UserInterface $account) {
Notifly::send(
"👤 Регистрация: {$account->getAccountName()}",
"Email: {$account->getEmail()}\n" .
"Roles: " . implode(', ', $account->getRoles()),
5
);
}
/**
* Ошибки PHP / watchdog уровня ERROR и выше.
*/
function notifly_alerts_logger_factory_alter(&$factory) {
// см. реализацию ниже — нужен отдельный сервис-логгер
}

Чтобы ловить вообще все события, добавим собственный logger. Создайте notifly_alerts.services.yml:

services:
logger.notifly:
class: Drupal\notifly_alerts\Logger\NotiflyLogger
arguments: ['@logger.log_message_parser']
tags:
- { name: logger }

И src/Logger/NotiflyLogger.php:

<?php
namespace Drupal\notifly_alerts\Logger;
use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\Core\Logger\RfcLoggerTrait;
use Drupal\notifly_alerts\Notifly;
use Psr\Log\LoggerInterface;
class NotiflyLogger implements LoggerInterface {
use RfcLoggerTrait;
public function __construct(private LogMessageParserInterface $parser) {}
public function log($level, string|\Stringable $message, array $context = []): void {
// 0=Emergency, 3=Error.
if ($level > 3) return;
$vars = $this->parser->parseMessagePlaceholders($message, $context);
$body = strtr((string) $message, $vars);
Notifly::send(
sprintf('🐞 %s [%s]', $context['channel'] ?? 'php', match ((int)$level) {
0,1 => 'CRITICAL',
2 => 'CRITICAL',
3 => 'ERROR',
default => 'WARN',
}),
$body . "\n\nRequest: " . ($context['request_uri'] ?? '-'),
$level <= 2 ? 10 : 8
);
}
}

После добавления — drush cr. Все события watchdog уровня ERROR/CRITICAL/EMERGENCY будут уходить в Notifly.

Создайте config/install/notifly_alerts.settings.yml:

url: 'https://your-notifly.example.com'
token: 'AGdjfk_L.dKe8q'

И страницу настроек в админке — это уже стандартный паттерн ConfigForm Drupal.

Можно использовать встроенный cache.default:

$cache_id = 'notifly_throttle:' . md5($title);
if (\Drupal::cache()->get($cache_id)) return;
\Drupal::cache()->set($cache_id, 1, time() + 60);
Notifly::send(/* ... */);
  • Полная видимость watchdog в кармане. Не нужно лазить в Reports → Recent log.
  • Узлы и регистрации в реальном времени. Удобно для модерации.
  • Дешёвая альтернатива внешним сервисам ошибок.
  • Отдельный канал Notifly для контента и для технических событий.
  • Включать ID узла/пользователя в extras, чтобы клиент Notifly мог предоставлять deep-link.