CTO Vetmanager, PHP Developer, Ironman 70.3

Botman — пишем чатбот на php

Компания ЭФКО организовывает митап посвященный чатботам и меня пригласили выступить с докладом - Botman — пишем чатбот на php.

Как обычно изложу свои мысли сначала текстом и начну с конца, как в хорошем ООП.

Выводы

Botman - framework для разработки чатботов. Официальный сайт даже завявляет - что это самый популярный PHP фреймворк, не могу это доказать или опровергнуть.

Botman - работает.

Вам он подойдет, если:

Нужен бот под разные платформы. Не стоит выбирать ботман, если вам нужен конкретно телеграмм бот, например. Хотя это достаточно легковесная платформа, но излишняя сложность ради кроссплатформенности все равно присутствует.

Вы не планируете на десятилетия вперед. Я считаю, что ботман не будет активно развиваться в будущем. Если у вас долгосрочные планы, это тоже повод задуматься.

Логика уже написана на php. Возможно вам подойдет какой-то конструктор ботов? Если у вас нет готовых пакетов которые вы будете использовать в этом фреймворке есть смысл задуматься о nocode решении.

Нет претензий к интерфейсу. В угоду кроссплатформенности урезаны практически любые возможности форматирования сообщений. Об этом еще поговорим.

Можно было закончить на этом, но давайте углубимся еще немного.

Преимущества

Приятный синтаксис

$botman->hears('Call me {name}', function($bot, $name) {
    $bot->reply('Hi ' . $name);
});

Мне нравится как это выглядит. Ничего лишнего.

Кроссплатформенность

Единый код будет работать под разные платформы. Есть драйвера под главные Telegram, Facebook, VK … на github можно поискать еще драйверов для botman. Судя по-всему драйвера пишутся достаточно легко, хотя я не пробовал.

Middlewares

Можно писать собственный мидлвари для входящих и исходящих сообщений.

use BotMan\BotMan\Interfaces\Middleware\Received;

class MyMiddleware implements Received
{
    public function received(IncomingMessage $message, $next, BotMan $bot)
    {
        ...
        return $next($message);
    }
}

Это удобно для организации статистики вашего ЧатБота. Очень рекоммендую подумать какие ваши целевые показатели и что вам нужно логировать.

Мидлвари можно применять ко всем сообщениям или к некоторым группам. Все достаточно очевидно.

Conversations

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

$botman->hears('Hello', function($bot) {
    $bot->startConversation(new OnboardingConversation);
});

class OnboardingConversation extends Conversation
{
    protected $firstname;
    protected $email;

    public function run()
    {
        $this->askFirstname();
    }

    public function askFirstname()
    {
        $this->ask('Hello! What is your firstname?', function(Answer $answer) {
            $this->firstname = $answer->getText();
            $this->askEmail();
        });
    }

    public function askEmail()
    {
        $this->ask('One more thing - what is your email?', function(Answer $answer) {
            $this->email = $answer->getText();
            $this->say('Great - that is all we need, '.$this->firstname);
        });
    }
}

Между запросами экземпляр класса сериализируется и хранится в хранилище ботмана. Единый класс работает между запросами, это очень удобно, не нужно думать про хранение данных между запросами. Но будьте внимательные, не работает Dependency Injection, придется делать new там где вам нужен объект или не забыть про __wakeup.

Storing information

Есть простое хранилище, которое можно использовать тут же в диалоге и которое связано с пользователем. С хранилищами не все гладко, подробнее расскажу в недостатках.

$bot->userStorage()->save(['gg' => 'hh']);
echo $bot->userStorage()->get('gg');

Есть отдельное хранилище данных канала и отдельное для данных пользователя. Это удобно.

Botman Studio

Это тул, который позволяет генерировать бандлы ботмана с Laravel. Что дает немного больше возможностей.

composer global require "botman/installer"
botman new weatherbot
  1. Веб-морда на Laravel. Можно вывести статистику или админку бота.
  2. Tinker - крутой инструмент для проверки бота. Можно запускать прям в консоли, также доступен и в веб-интерфейсе. Это прям супер плюс.
  3. Удобная работа с драйверами
  4. Написание feature тестов, которые можно запускать в githubactions
    public function testBasicTest()
    {
        $this->bot
            ->receives('Hi')
            ->assertReply('Hello!');
    }

ngrok - это не связанный с ботманом сервис, но если не не слышали про него обязательно посмотрите что он умеет.

Недостатки

Тут тоже есть что рассказать.

Фреймворк умирает

Фреймворк активно развивался в 2017-2018. Code-Frequency

Много отрытых задач и незакрытых PR. Репозиторий если и не выглядит совсем заброшенным, то точно не очень ухожен.

В момент установки в композер вы увидите много некрасивых ворнингов.

Нет возможности верстать сообщения

Возможности верстки очень ограничены. Вы сможете работать с картинками, файлами, локациями, текстом и кнопками, но при этом у вас нет никаких инструментов форматирования. Только PHP_EOL.

Фреймворк позволяет кастомизировать сообщения для каждой платформы отдельно используя функционал “рецептов”, но я бы уже задумался на этом этапе стоит ли выбирать Ботман, если вам нужны recipes.

Хранилище очень ограничено в использовании

По-умолчанию вы можете хранить данные в File или Redis. Вы можете написать и свой драйвер хранилища.

Файловое хранилище для пользователя работает подобно сессиям. Это файл на диске, каждому пользователю свой файл.

Рекоммендую постоянное хранение данных делать самостоятельно и без Ботман.

При этом нельзя просто взять и изменить значение какой-то переменной в хранилище пользователя, нужно обязательно сохранять все данные.

Tinker не позволяет протестировать все

Тинкер - позволяет тестировать переписку с ботом. Но отладить какое-то консольное приложение у вас уже не получится. Метод say, который нужен чтобы отправлять сообщения заданным получателям не работает с ним.

Лайфхаки

Рекомендации по работе в Heroku

  1. Не надейтесь на хранилище Ботман
  2. Используйте MySQL для БД, удобно и бесплатно
  3. Redis и MySQL в хероку это ссылка, которая хранится в переменных окружения. Не забудьте в конфиге laravel организовать парсинг.
  4. Чтобы использовать redis в хероку, добавьте в композер "ext-redis": "*", удалите из config/app.php алиас на Redis
  5. Команды: heroku run -aapp-name bash, heroku vim -aapp-name
  6. Бесплатный крон в хероку запускается жутко не вовремя и не чаще чем раз в 15 минут. Учитывайте это.

Как хранить данные “вечно”?

Лучше не нужно, но если очень хочется. Используйте Redis и в файле config/botman/config.php нужно выставить бесконечное хранение данных.

    'user_cache_time' => 0,

Еще вам придется переопределить резолвинг botman, поскольку пользовательские данные он все равно хранит в файлах.

В драйвере Redis много захардкожено и вам может быть придется писать свой если у вас какие-то есть приколы, на heroku botman нормально работает только с сервисом “Redis Cloud”, не перепутайте!

Как дебажить?

Хорошая статья

Как писать в Storage из console?

Если вы хотите не только читать, но и писать в хранилище Пользоваля от Botman из консоли(Например после рассылки по крону сохранять результат), то предварительно сохраните в хранилище информацию sender.

    $bot->userStorage()
        ->save([
            'sender_id' => $bot->getMessage()->getSender()
        ]);

Тогда вы сможете получить данные из всех хранилищ пользователей

$storages = $bot->userStorage()->all();
foreach ($storages as $userData) {
    $senderId = $userData->get('sender_id');
    $storage = new Storage(resolve('redisStorage'));
    $storage->setPrefix('user_')
        ->setDefaultKey($senderId);

Все теперь в storage можно писать.

Как тестировать Botman в github actions?

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

    config('app.env') === 'testing'
        ? new FileStorage(storage_path('botman'))
        : resolve('redisStorage');

Создайте .env.testing с нужным окружением и копируйте его первым шагом вашей сборки.

  - name: Create env file
    run: |
      cp .env.testing .env
      cat .env

Как тестировать ответы?

Я не люблю писать тесты на полное соответсвие ответа с эталоном. Это хрупкие тесты и они склонны ломаться. Встроенны методы BotManTester не позволяют этого сделать - но можно достать ответ и преобразовать его в текст самостоятельно.

        $messages = $this->bot
            ->receives('Hello')
            ->getMessages();
        $this->assertStringContainsString( 'привет', $messages[0]->getText());

Ссылки

  1. Botman
  2. Vetmanager Botman
  3. En Ru Vocabulary