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

Middleware (Промежуточное ПО) ​

Middleware (промежуточное ПО) — это мощный механизм, который позволяет вам выполнять код до того, как будет обработан основной обработчик маршрута. Это функции, которые встраиваются в цепочку обработки запроса и могут модифицировать его, проверять данные или прерывать выполнение, если определённые условия не выполнены.

Основные сценарии использования Middleware:

  • Аутентификация и авторизация: проверка, имеет ли пользователь доступ к определённой команде или функции.
  • Логирование: запись информации о входящих запросах.
  • Подготовка данных: загрузка данных пользователя из базы данных и передача их в основной обработчик.
  • Ограничение частоты запросов (Rate Limiting): предотвращение спама от пользователей.

В библиотеке существует два типа Middleware: глобальное и для конкретного маршрута.

Глобальное Middleware ​

Глобальное Middleware устанавливается на весь экземпляр бота с помощью метода $bot->middleware() и выполняется для каждого входящего обновления, прежде чем библиотека начнёт поиск подходящего маршрута.

Сигнатура ​

php
public function middleware(callable $handler): void

Обработчик Middleware принимает два аргумента:

  1. $tg (ZenithGram\ZenithGram\ZG): Экземпляр основного класса ZG, предоставляющий доступ ко всему контексту и методам API.
  2. $next (Closure): Функция обратного вызова. Вы должны вызвать $next(), чтобы передать управление дальше по цепочке (к поиску маршрута). Если $next() не будет вызван, обработка обновления прекратится.

Пример: Проверка регистрации пользователя ​

Представим, что у вас есть бот, которым могут пользоваться только зарегистрированные пользователи. Глобальное Middleware — идеальное место для такой проверки.

php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ZenithGram\ZenithGram\ZG;
use ZenithGram\ZenithGram\Bot;

// Предположим, у нас есть функция для проверки пользователя в БД
function isUserRegistered(int $user_id): bool {
    // ... ваша логика проверки в базе данных
    // Для примера, разрешим доступ только пользователю с ID 12345
    return $user_id === 12345;
}

const ADMIN_IDS = [12345, 54321]; // Список ID администраторов

$tg = ZG::create(BOT_TOKEN);
$bot = new Bot($tg);

// Устанавливаем глобальное Middleware
$bot->middleware(function (ZG $tg, Closure $next) {
    $user_id = $tg->getUserId();

    if ($user_id && !isUserRegistered($user_id)) {
        // Если пользователь не зарегистрирован, отправляем ему сообщение
        // и НЕ вызываем $next(), прерывая дальнейшую обработку.
        $tg->msg('Извините, для использования этого бота требуется регистрация.')->send();
        return;
    }

    // Если проверка пройдена, передаем управление дальше
    $next();
});


$bot->onBotCommand('/start')->text('Привет, зарегистрированный пользователь!');
$bot->onBotCommand('/help')->text('Это раздел помощи.');

В этом примере, если пользователь с ID, отличным от 12345, напишет боту /start или любую другую команду, он получит сообщение о необходимости регистрации, а сам обработчик команды не выполнится.

Middleware для маршрута ​

Иногда проверку нужно выполнить только для определённой команды или группы команд. Для этого можно использовать Middleware на уровне конкретного маршрута, применив метод middleware() к объекту Action.

Такое Middleware выполняется после того, как маршрут был успешно сопоставлен с запросом, но до вызова основного обработчика, заданного методом func().

Сигнатура ​

php
public function middleware(callable $handler): Action

Обработчик также принимает аргументы $tg и $next.

Пример: Команды только для администратора ​

Допустим, у нас есть команда /admin, которая должна быть доступна только администраторам бота.

php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ZenithGram\ZenithGram\ZG;
use ZenithGram\ZenithGram\Bot;

const ADMIN_IDS = [12345, 54321]; // Список ID администраторов

$tg = ZG::create(BOT_TOKEN);
$bot = new Bot($tg);

$bot->onBotCommand('/start')->text('Добро пожаловать!');

// Создаем маршрут для админ-панели и добавляем к нему Middleware
$bot->onBotCommand('/admin')
    ->middleware(function (ZG $tg, Closure $next) {
        $user_id = $tg->getUserId();

        if (!in_array($user_id, ADMIN_IDS)) {
            // Если пользователь не админ, отправляем сообщение
            // и НЕ вызываем $next().
            $tg->msg('⛔️ У вас нет прав доступа к этой команде.')->send();
            return;
        }

        // Пользователь - админ, продолжаем выполнение
        $next();
    })
    ->text('Добро пожаловать в панель администратора!');

Важно

В отличие от методов access() и noAccess(), которые являются простыми проверками по ID, Middleware позволяет реализовать гораздо более сложную логику: запросы к базе данных, проверку подписки на канал, проверку времени суток и многое другое.

Порядок выполнения ​

Чтобы лучше понять, как все работает вместе, вот последовательность выполнения:

  1. Приходит новое обновление от Telegram.
  2. Выполняется глобальное Middleware (если установлено).
    • Если оно не вызывает $next(), обработка прекращается.
  3. Библиотека ищет подходящий маршрут (onBotCommand, onText и т.д.).
  4. Если маршрут найден, выполняется Middleware этого маршрута (если установлено).
    • Если оно не вызывает $next(), обработка прекращается.
  5. Выполняется основной обработчик маршрута (установленный через func() или встроенная отправка сообщения через text(), kbd() и т.д.).

Опубликовано под лицензией MIT.