Repository navigation
Releases: geekcodev/laravel-max-client
Release list
v1.2.1
v1.2.1
Пакет переведён в полосу 0000_00 — начало общего порядка миграций приложения и всех пакетов. Позиция выведена из графа
внешних ключей, а не из того, что пакет ставится первым.
Новое
Полоса 0000_00. Полоса в имени миграции — это позиция владельца в общем порядке, а не отдельный именованный
диапазон. Laravel сортирует миграции приложения и всех пакетов по имени файла и применяет их сверху вниз, поэтому
внешний ключ допустим только на таблицу, чья миграция имеет меньшее имя. Раскладка стека i2tech:
0000_00 — laravel-max-client: max_users, max_chats, max_chat_users
0000_01 — приложение, системные таблицы (users и прочие)
0000_02 — filament-max-chat
0000_03 — filament-max-broadcasts
0000_04 — приложение, доменные таблицы
Пакет занимает начало порядка, потому что на его таблицы ссылаются все: users.max_user_id указывает на
max_users.user_id (системные миграции приложения, 0000_01), max_chat_messages.max_chat_id — на max_chats
(плагин, 0000_02), max_broadcasts.created_by — на users (0000_01). Если бы max_users создавалась позже
users, внешний ключ в users упирался бы в ещё не существующую таблицу.
Документ с раскладкой полос и графом FK продублирован в docblock первой миграции пакета: тот, кто чинит порядок, сначала
читает файл, который для этого и существует.
Изменение (BC)
Переименованы все три миграции пакета:
| было | стало |
|---|---|
0001_01_01_000001_create_max_users_table |
0000_00_000001_create_max_users_table |
0001_01_01_000002_create_max_chats_table |
0000_00_000002_create_max_chats_table |
0001_01_01_000003_create_max_chat_users_table |
0000_00_000003_create_max_chat_users_table |
Объявления таблиц не менялись. Для обновляющейся установки переименование означает, что все три миграции числятся
невыполненными при уже существующих таблицах, поэтому в up() каждой добавлен Schema::hasTable() с выходом по
return: таблицы не пересоздаются, миграции просто отмечаются выполненными. Без этого guard смена имени ломает ровно те
установки, ради которых делается, — они падают на повторном Schema::create.
Guard останавливает только пересоздание таблицы. Колонки, свёрнутые в create миграций 9570064
(max_users.phone_verified_at, метаданные чатов, индекс по chat_id), он не возвращает — их доставляет
max:upgrade: команда добавляет phone_verified_at, если его нет, и пересобирает max_chats только из реально
существующих колонок. Поэтому порядок migrate, затем max:upgrade обязателен, а migrate в одиночку оставляет схему
неполной.
Порядок обновления не изменился: php artisan migrate, затем php artisan max:upgrade. Вторая команда на чистой базе
остаётся no-op.
В таблице migrations строки со старыми именами остаются как неиспользуемые: на migrate они не влияют, но при желании
их можно удалить вручную.
Если до v1.1.4 миграции пакета публиковались в приложение, копии лежат под старыми именами и перекрывают файлы пакета:
Migrator::getMigrationFiles() собирает файлы через keyBy() по имени, и путь приложения идёт позже пути пакета. Копии
нужно удалить — порядок описан в README.md, раздел «Если миграции пакета публиковались раньше».
Затронутые сценарии
- Чистая установка:
php artisan migrateсоздаётmax_users,max_chats,max_chat_usersдо того, как приложение
создастusersи начнёт ссылаться наmax_users. - Обновление с v1.2.0: три миграции применяются повторно, каждая выходит по
Schema::hasTable(), данные и схема не
меняются. - Обновление с v1.1.x: сначала
php artisan migrate, затемphp artisan max:upgrade— порядок обязателен, между шагами
схема неполна. - Обновление с v1.2.1: ничего не делает, второй прогон — no-op.
Качество
tests/Feature/MaxMigrationsTest.php: полоса0000_00(guard по исходникам всех файлов каталога), знаковость
идентификаторов, отсутствие ad-hoc миграций. На SQLite нарушение порядка не воспроизводится — таблица с внешним ключом
на несуществующую таблицу создаётся молча, — поэтому проверка идёт по именам файлов, а не по результату
migrate.tests/Feature/MaxSchemaUpgradeTest.php: сценарии обновления старой формы реестра, слияние дублей, идемпотентность,
откат. Исторические имена миграций в сценариях старых установок оставлены как есть — они воспроизводят реальное
состояние базы хоста.- PHPUnit, PHPStan, PHP CS Fixer — без ошибок.
- Документация:
README.md— раздел «Полоса миграций и порядок установки» и переработанный раздел «Если миграции пакета
публиковались раньше»;AGENTS.md— правило миграций в структуре репозитория и gotchas 19–20 (порядок миграций и
переименование выполненной миграции).
v1.2.0
v1.2.0 — Реестр чатов: одна строка на чат
Релиз адаптера geekcodev/laravel-max-client поверх ядра geekcodev/max-php-client: ^1.1.8. Предыдущий тег — v1.1.4.
Версия MINOR, ломающих изменений два: они описаны в разделе «Изменение (BC)».
Новое
- Таблица связей
max_chat_users: модельModels\MaxChatUser, настройкаchats.chat_users_model
(MAX_CHAT_USERS_MODEL). Это реестр зафиксированных взаимодействий пользователя с ботом, а не состав группы или
канала: строки появляются только из апдейтов, в которых пришёл конкретныйuser_id. - Связи моделей:
MaxChat::maxUsers()иMaxChat::chatUsers(),MaxUser::chatLinks().
MaxUser::maxChats()сохранена и теперь проходит черезmax_chat_users(внутриhasManyThrough). - Аддитивная миграция
0001_01_01_000003_create_max_chat_users_table— новая таблица. - Команда
php artisan max:upgrade— перенос данных существующих установок: пары «чат + пользователь» уезжают
вmax_chat_usersвместе со статусом иlast_activity_at, дубли чата сливаются в одну строку, после чегоuser_id
и суррогатныйidудаляются, аchat_idстановится первичным ключом. Опции:--dry-run(план без применения),
--force(без подтверждения),--rollback(вернуть прежнюю форму). Логика —Services\MaxSchemaUpgrade,
команда идемпотентна. - Схема реестров: у
max_chatsпервичный ключ — самchat_idиз MAX (строка одна на чат, суррогатногоidнет),
уmax_users—user_id. Единственный суррогатный ключ —idстроки связиmax_chat_users:uuid(uuid7, значение
выдаёт модель черезHasUuids). Отдельный индекс наmax_chat_users.chat_idне добавляется: он полностью покрыт
левым префиксом UNIQUE(chat_id, user_id). - Индексы на
max_chats.statusиmax_chats.last_activity_at— по ним работаютactiveChatIds()и
max:chats:refresh. Установленным проектам их добавляетmax:upgrade, чтобы схема после обновления совпадала
со схемой чистой установки.
Изменение (BC)
max_chatsхранит одну строку на чат. Прежде в ней лежала строка на пару «пользователь + чат» с уникальным ключом
(user_id, chat_id). Колонкиuser_idи суррогатныйidудалены, первичным ключом сталchat_id.- Колонка
max_chats.title_checked_atпереименована вchat_checked_at. Отметка ставится один раз на проверку
getChatи относится ко всем метаданным сразу (название, описание, ссылка, иконка), а не к одному названию.
Переименование выполняетmax:upgradeс сохранением значения времени (в--rollbackпрежнее имя возвращается,
иначе код v1.1.4 упал бы на отсутствующей колонке). Установка v1.1.3 и старше аддитивных миграций метаданных не
получала: пересборка читает только существующие колонки, поэтому обновление проходит, а чат попадает в очередь
max:chats:refreshодин раз. - Настройка интервала перепроверки переименована:
chats.title_check_interval→chats.chat_check_interval,
MAX_CHATS_TITLE_CHECK_INTERVAL→MAX_CHATS_CHAT_CHECK_INTERVAL. Прежние имена остаются рабочими: если нового
ключа в конфиге нет вовсе (конфиг опубликован до v1.2.0), читается старый; если новая переменная окружения пуста
(так заполнен.env.example), читается прежнее имя переменной. Явно выставленный новый ключ — в том числе0—
всегда побеждает прежний, иначе переопределить старый интервал было бы нечем. - Суррогатный
idстрокиmax_chatsудалён, аchat_idстал первичным ключом. Смена первичного ключа
выполняется пересборкой таблицы (временная таблица в целевой форме, копирование однимinsert … select,
переименование): на месте ключ сменить нельзя — SQLite отклоняет удаление колонки ключа («cannot drop PRIMARY KEY
column») и молча игнорируетprimary()внеcreate, MySQL требует отдельных команд.MaxChatбольше не использует
HasUuids:$primaryKey = 'chat_id',$incrementing = false,$keyType = 'int'. Уmax_chat_usersсуррогатный
uuid-ключ остаётся — идентификаторов от MAX у строки связи нет, поэтому вставка идёт через Eloquent: голый
DB::table('max_chat_users')->insert()безidне сработает, у колонки нет значения по умолчанию на стороне базы. MaxChat::maxUser()удалён — метод был привязан к прежней форме. Замена:$chat->maxUsers()(HasManyThrough) или
$chat->chatUsers()->first()?->maxUserдля первого пользователя.max:upgradeвместо реструктурирующей миграции. Перенос данных делает команда, а не файл в таблице
migrations: перед изменением схемы виден план (--dry-run), результат откатывается (--rollback), а у потребителя
не появляется лишняя запись вmigrations. Обновление стало двухшаговым:php artisan migrate(создаёт таблицу
связей) иphp artisan max:upgrade(переносит данные). Ручные правки схемы команда, как и прежняя её версия,
не закрывает.MaxChatProfileService::inheritKnownMetadata()удалён: с одной строкой на чат наследовать нечего.- Create-миграции
000001/000002переписаны под новую схему. Для новых установок это обычные миграции; у
установленных проектов имена000001/000002числятся выполненными, поэтому их форму меняет миграция000004.
Их форму меняетmax:upgrade. У потребителей, которые копировали миграции вручную (см. README, раздел «Если миграции
пакета публиковались раньше»), копии обязательно удаляются, иначе они перекроют файлы пакета. - Апдейт без объекта
userбольше не пишетmax_users, но строка чата заводится и обновляется, а связь
max_chat_users— только при известномuser_id.
Исправления
bot_removedбезuserбольше не теряется и для неизвестного чата. Прежде новая строка не заводилась, потому что
user_idбыл частью уникального ключа; чат навсегда оставалсяactive,activeChatIds()его возвращал, а
max:chats:refreshбесконечно опрашивалgetChat. Теперь строка чата создаётся по одномуchat_id.- Метаданные чата запрашиваются один раз на чат. Проверка «есть ли заполненная строка» больше не зависит от числа
участников: 50 участников группы дают одинgetChat, а не пятьдесят. MaxChat::displayName()при пустомtitleберёт имя первого известного пользователя чата.- Колонки идентификаторов объявлены знаковым
BIGINT. Преждеchat_idиuser_idбылиunsigned, а
идентификаторы групп и каналов в MAX отрицательные (владелец прислал реальный-79032376695376): на MySQL такой
чат в реестр не попадал (Out of range value), причём SQLite знаковость игнорирует, поэтому тесты баг не видели.
Знаковым объявлены иuser_id— идентификаторы пользователей положительные, но знаковая колонка принимает всё, что
принималаunsigned. Существующим установкам правит та же командаmax:upgrade:chat_id— пересборкой,
max_users— шагомchange()на месте без переобъявления первичного ключа. Отдельной миграции ради смены типа
не заводили. Знаковость колонок сама по себе проблему не закрывала: отрицательные идентификаторы отбрасывались
ещё и фильтрами вMaxChatProfileService,MaxChatsRefreshCommandиMaxUserProfileService, поэтому название
группы и канала не запрашивалось никогда (в апдейтах его нет, аgetChat()— единственный источник), свежая
отметкаchat_checked_atне учитывалась, и профили участников групп и каналов не заполнялись. Фильтры убраны,
отбрасывается только0— такого идентификатора в MAX не бывает.
Тем же приёмом команда возвращаетmax_users.phone_verified_at, если колонки нет: её создавала
аддитивная миграция v1.1.4, у установки v1.1.3 её не будет, аPersistMaxUserPhoneListenerпишет отметку
безусловно — без возврата запись телефона падала бы с «no such column».
Затронутые сценарии
- Обновление существующей установки: два шага —
php artisan migrate(создаётmax_chat_users) и
php artisan max:upgrade(переносит данные). Данные реестров сохраняются: пары переносятся, дубли чатов сливаются
по правилам «первые непустые метаданные,max(last_activity_at),min(created_at), статусremoved > stopped > active». Между шагами схема неполна —max_chatsещё в прежней форме, а модель уже ждёт новую, поэтому порядок
обязателен. Команда идемпотентна: повторный запуск на новой форме ничего не меняет. - Новая установка: публиковать миграции не нужно,
loadMigrationsFromподхватывает их из пакета; достаточно
одногоphp artisan migrate,max:upgradeне требуется. - Откат
max:upgrade --rollback: симметричен — он тоже пересобирает таблицу, поэтому возвращает и прежнюю форму
(строка на пару,user_id, счётчикidв auto-increment), а не только набор колонок.user_idстановится
nullable: чат без зафиксированных пар получает строку сNULL— терять факт чата хуже, чем падать на откате. Значения
прежних ключей как числа не восстанавливаются (они не сохранялись), но повторный запуск команды снова приводит реестр
к новой форме. Колонкаmax_users.user_idсужается обратно только если в таблице нет отрицательных
идентификаторов, — иначе шаг пропускается, потому что сузить их молча означало бы потерять данные. - Удаление бота из чата: чат переходит в
removedнезависимо от наличияuserв апдейте, и больше не
опрашиваетсяmax:chats:refresh. - Диалог и группа:
displayName()возвращаетtitle, а при пустомtitle— имя собеседника или первого
известного пользователя. - Профиль пользователя:
MaxUserProfileService::refresh()берёт активныеchat_idизmax_chats, а
пользователей — изmax_chat_users. - Логирование не публикует персональные данные. При явно включённом
logging.log_request_bodyтело апдейта
с вложением-контактом содержит телефон и содержимое vCard; маскирование знало только про токены, поэтому ключи
phone,vcf_infoиvcf_phoneдобавлены в список секретных (A09).
Качество
- PHP 8.4,
declare(strict_types=1), PSR-12, PHPStan level max. composer run lint— 0 файлов с пр...
v1.1.4
v1.1.4 — Метаданные чатов, телефон из контакта, обновление схемы
Релиз адаптера geekcodev/laravel-max-client поверх ядра geekcodev/max-php-client: ^1.1.8. Предыдущий тег — v1.1.3.
Версия PATCH: только аддитивные изменения и исправления, за исключением двух конструкторов, перечисленных в разделе
«Изменение (BC)».
Новое
- Метаданные чатов в
max_chats: колонкиtitle,description,link,icon_url,
title_checked_at. Заполняются изgetChat()приbot_added/bot_startedи из самого апдейта при
chat_title_changed. Один ответgetChatпишется во все записи чата, поэтому в группе на 50 участников делается один
запрос, а не пятьдесят. Пустой ответ не затирает уже известное значение. MaxChatProfileService:sync(),ensureMetadata(),inheritKnownMetadata(),refresh(),
pendingChatIds(),fetch(),chatTypeFor(),activeChatIds(),forgetChatCache(),
hasKnownTitle(). Настройкиchats.fetch_metadata(MAX_CHATS_FETCH_METADATA, по умолчаниюtrue) и
chats.title_check_interval(MAX_CHATS_TITLE_CHECK_INTERVAL,0— проверять все чаты).MaxContactService: проверка целостности contact-вложения ядром (ContactVerifier) и извлечение телефона
(ContactPhoneExtractor, с v1.1.7). Входящего поляvcf_phoneв апдейтах нет, телефон разбирается изvcf_info.- Телефон в
max_users: полеphone_verified_atи сохранение номера из подтверждённого
request_contact. Отдельного флага включения нет — запись заводится, если её не было, отличающийся номер
перезаписывается (смена номера аккаунта), совпадающий обновляет только отметку проверки. Значение сохраняется как
есть, без канонизации; телефон иvcf_infoв лог не попадают. php artisan max:chats:refresh [--all|--chat=ID]: дозаполнение и перепроверка метаданных чатов.php artisan max:upgrade [--dry-run]: идемпотентная проверка и дополнение схемы реестров для
рассинхронизированных или ранее опубликованных миграций. В штатном обновлении не нужен.- Индекс по
chat_idвmax_chatsотдельной миграцией. - Additive-миграции вместо правки create-миграций:
0001_01_01_000003_add_phone_verified_at_to_max_users_table,
0001_01_01_000004_add_chat_id_index_to_max_chats_table,
0001_01_01_000005_add_chat_metadata_to_max_chats_table. Ранее выполненные create-миграции больше не правятся на
месте. Миграции загружаются провайдером (loadMigrationsFrom), публикация не требуется.
Изменение (BC)
- Ничего не удалено: сверка публичного API с тегом
v1.1.3даёт 0 удалённых методов. - Два конструктора изменились — документированное исключение из правила «в patch-релизе сигнатуры не меняются».
Сохранение прежних сигнатур потребовало бы держать вPersistMaxChatListener
зависимостьApiClient, которая больше не используется, поэтому решено оставить чистые сигнатуры и пометить классы
как внутренние:
| Класс | v1.1.3 | v1.1.4 |
|---|---|---|
Listeners\PersistMaxChatListener |
(Config, ApiClient) |
(Config, MaxChatProfileService) |
Services\MaxUserProfileService |
(ApiClient, Config, Logger) |
(ApiClient, Config, Logger, MaxChatProfileService) |
Оба класса собираются контейнером, и правка в MaxServiceProvider внесена для каждого по-своему:
MaxUserProfileService получает четвёртую зависимость в привязке register(), а
PersistMaxChatListener в привязке не участвует — он регистрируется как класс слушателя в boot(), поэтому
переопределять его нужно подменой привязки на MaxUpdateReceived, а не подменой класса. Кто собирает любой из двух
классов вручную, обязан передавать новый набор зависимостей.
- Поведение
bot_stopped/bot_removedисправлено (см. «Затронутые сценарии»), обратная совместимость не нарушена: для
апдейтов сuserповедение прежнее. Config::usersCapturePhone()не выпускался ни в одном теге, поэтому его удаление вместе с
MAX_USERS_CAPTURE_PHONEне является ломающим изменением.
Исправления
bot_removedбезuserбольше не теряется.bot_added/bot_started/bot_stopped/bot_removed— события о
боте в чате, а не о пользователе, поэтомуuserв них может отсутствовать. Прежде слушатель пропускал такой апдейт,
чат навсегда оставалсяstatus = active,activeChatIds()продолжал его возвращать, аmax:chats:refreshбесконечно
опрашивалgetChat. Теперь приuserId === nullстрока не создаётся (user_idчасть уникального ключа), но
status,last_activity_atиchat_type
обновляются во всех записях с этимchat_id.- Новая запись участника наследует метаданные чата без запроса. Проверка перед запросом отвечала на вопрос «есть ли
хоть одна заполненная строка», а заполнение шло по всем строкам сchat_id, поэтому все участники после первого
оставались без названия. Правило в три состояния: неизвестно нигде →
getChatи запись всем; часть пуста → наследование без запроса; заполнены все → ничего. Существующие значения не
затираются,title_checked_atпереносится вместе с данными. phoneне затирается вupsertUser()иMaxUserProfileService::upsertFromMember()— там перечислены явно
обновляемые поля.
Затронутые сценарии
- Обновление существующей установки: достаточно
php artisan migrate. Аддитивные миграции видны как невыполненные,
данные сохраняются.php artisan max:upgrade— страховка для рассинхронизированной или ранее опубликованной схемы,
идемпотентна. - Новая установка: публиковать миграции не нужно,
loadMigrationsFromподхватывает их из пакета. - Удаление бота из чата: чат переходит в
removedнезависимо от наличияuserв апдейте, и больше не опрашивается
max:chats:refresh. - Группа с несколькими участниками: один запрос
getChatна чат, метаданные у всех записей, включая записи
участников, пришедших позже. - Контакт в сообщении: телефон из подтверждённого
request_contactпопадает вmax_users.phone; неподтверждённое
вложение игнорируется; пересланные контакты приходят файлами и в этот путь не попадают. - Слушатели:
PersistMaxUserPhoneListenerрегистрируется всегда и независимо отchats.enabled. Приложения,
полагавшиеся на отсутствие listeners дляMaxUpdateReceived(например, на ручную постановку в очередь), учитывают это
при обновлении.
Качество
- PHP 8.4,
declare(strict_types=1), PSR-12, PHPStan level max. composer run lint— 0 файлов с правками (62 файла).vendor/bin/phpstan analyse— 0 ошибок.vendor/bin/phpunit— 298 тестов, 732 утверждения, все зелёные.composer run coverage— 98.11% строк (1041/1061), 90.97% методов, порог 95%.composer audit— 0 уязвимостей.- Guard-тесты схемы:
testCreateMigrationsStayFrozen(create-миграции не правятся),
testNewColumnsAreDeclaredInAdditiveMigrationsOnly,testChatIdIndexHasItsOwnMigration, сценарий обновления
установленной версии с сохранением данных и rollback--step 3. - Проверено сравнением публичного API с тегом
v1.1.3: удалённых методов 0.
v1.1.3
v1.1.3 — Синхронизация с ядром v1.1.6 (Comments API, getUpdatesBatch)
Новое
- Зависимость ядра:
geekcodev/max-php-clientповышен^1.1.0→^1.1.6. Через фасадMaxдоступны новые методы
ядра: Comments API (getComments,getComment,sendComment,editComment,deleteComment) и
getUpdatesBatch(батчевое получение апдейтов,UpdatesResult). chat_typeиз comment-апдейтов:PersistMaxChatListenerдозаполняетmax_chats.chat_typeиз
Update::$comment->recipient->chatType(ядро с v1.1.6 присылает событияcomment_created/comment_edited/
comment_removedсrecipient—chat_id,chat_type,post_id). Как и для message/callback-апдейтов, статус не
меняется и чат не создаётся; неизвестныйchat_typeне перетирается.- Guard-тест фасад ↔ ядро:
MaxFacadeTest::testFacadeDocumentsEveryPublicApiClientMethodWithItsReturnType
сверяет PHPDocFacades\Maxсо всеми публичными методамиApiClientядра (имя + тип возврата) — расхождение больше
не проходит тесты молча при обновлении зависимости. - Логирование отклонённого вебхука: в
max.logпри 400 по невалидному payload добавленоmessageошибки API рядом
сexceptionиcode(без payload, токена и секрета). - Документация синхронизирована с ядром:
README.md(требованиеgeekcodev/max-php-client ^1.1.6, заполнение
chat_type),AGENTS.md(constraint^1.1.6, факты Comments API иgetUpdatesBatch, отсылка к guard-тесту).
Изменение (BC)
- Нет. Сигнатуры публичного API пакета не менялись, изменён только PHPDoc фасада (новые
@method). - Уточнены типы возврата в PHPDoc фасада по ядру v1.1.6:
uploadMedia()→UploadedInfo(вместоUploadResult),
sendAnswer()— с параметрамиnotificationиdisableLinkPreview. - Помечены deprecated
getChats()иaddChatMembers()(депозиция в ядре, удаление в v2.0.0) — методы по-прежнему
работают, адаптер их не вызывает.
Затронутые сценарии
- Комментарии к постам в каналах: чтение/правка/удаление через
Max::getComments(),Max::getComment(),
Max::sendComment(),Max::editComment(),Max::deleteComment(); права бота —read_all_messages
(токен админа канала). - Обработка
comment_created/comment_edited/comment_removedвMaxUpdateReceived: если чат уже в реестре, у него
проставляетсяchat_type; статус и состав реестра не меняются. - Батчевое чтение апдейтов вместо цикла
Max::getUpdates(). - Диагностика вебхука: в лог попадает сообщение ошибки API при отклонении payload.
Качество
- PHP 8.4, PSR-12 (lint — 0 файлов с правками), PHPStan level max — 0 ошибок.
- PHPUnit зелёные: 196 тестов / 463 assertions; покрытие строк 98.20% (655/667), порог ≥ 95%.
composer audit— без уязвимостей.- Интеграционные смоук-тесты (
--group integration) не запускались: нетMAX_API_TOKEN.
v1.1.2
v1.1.2 — Профиль для диалогов (dialog_with_user)
Новое
- Профили в диалогах:
MaxUserProfileServiceтеперь обрабатывает диалоги (chat_type = dialog), где
getChatMembersнедоступен («Method is not available for dialogs»). Для диалога источник полного профиля с аватаром —
getChat()→dialog_with_user(собеседник); профиль пишется вmax_usersчерез тот же
upsertFromMember(). Запись сохраняется только если собеседник входит в запрошенный списокuserIds. - Резолв типа чата (
chatTypeFor()): тип берётся из реестраmax_chats.chat_type; при неизвестном —
getChat()с записью в реестр (whereNull('chat_type'), без перетирания известного значения). Работает и в
refresh(), и вensureAvatar()без явногоchatId. - Кэш чатов (
chatFor()): один вызовgetChat()на chat_id в рамках прохода сервиса (chatCache), ошибки
логируются вmax.log(code + message, без чувствительных данных). - Зависимость ядра:
geekcodev/max-php-clientповышен^1.0.6→^1.1.0(DTOChatсdialog_with_user,
UserWithPhoto). - PHPDoc-контракт
MaxChat: задокументированы@property—user_id,chat_id,status,chat_type,
last_activity_at.
Изменение (BC)
- Нет. Новое поведение затрагивает только диалоги; группы/каналы используют прежний путь через
getChatMembers.
Затронутые сценарии
refresh()/ensureAvatar()для диалога: вместоgetChatMembers—getChat()+dialog_with_user; для
неизвестногоchat_typeтип резолвится автоматически и сохраняется в реестр чатов.
Качество
- PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
- PHPUnit зелёные (191 тест / 455 assertions); покрытие строк 98.20%, порог ≥ 95%.
composer audit— без уязвимостей.
v1.1.1
v1.1.1 — chat_type в реестре чатов
Новое
chat_type(nullablestring(16)) — колонка вmax_chats, тип чата:dialog/chat/channel. Каст в
ядерныйGeekCo\MaxPhpClient\Enum\ChatType.- Lifecycle-события (
bot_added/bot_started) заполняютchat_type:isChannel→Channel, иначе — fallback
getChat()API. При сбое значение остаётсяnull. - Входящие message/callback дозаполняют
chat_typeу существующего чата (whereNull), не создавая запись и не
меняяstatus. SRP сохранён: реестр отслеживает добавление бота, дозаполнение — молчаливая операция. - Publishable-миграция:
0001_01_01_000002_create_max_chats_table.phpдобавлена колонкаchat_type.
Изменение (BC)
- Нет. Колонка nullable, существующие записи не затронуты.
Качество
- PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
- PHPUnit зелёные (187 тестов / 436 assertions); покрытие строк 99.36%, порог ≥ 95%.
composer audit— без уязвимостей.
v1.1.0
v1.1.0 — Профиль пользователя (аватар), рефакторинг реестра чатов
Новое
MaxUserProfileService(src/Services/MaxUserProfileService.php, singleton в контейнере): наполнение
max_usersполноценным профилем —avatar_url,full_avatar_url,name,description— через эндпоинт ядра
getChatMembers(в апдейтах MAX аватар не приходит; источник истины — участники чата).refresh(int|list<int>): bool— chat_id из активныхmax_chats; если активного чата нет —falseбез вызова
API;upsertFromMember(ChatMember): MaxUser—updateOrCreateпоuser_id, пишетprofile_checked_at;ensureAvatar(MaxUser, ?int $chatId = null): bool— дозаполняет аватар, если пуст; явныйchatIdработает без
реестра; при уже заполненном аватаре ничего не вызывает.- Группировка userIds по чатам и батчинг по
users.profile_batch_size(по умолчанию 50) на вызовgetChatMembers.
- Конфиг
users.*:profile_from_active_chats(envMAX_USERS_PROFILE_FROM_ACTIVE_CHATS, defaulttrue) — резолв
chat_id из активныхmax_chats;profile_batch_size(envMAX_USERS_PROFILE_BATCH_SIZE, default50) — лимит
userIds на вызовgetChatMembers;profile_check_interval(envMAX_USERS_PROFILE_CHECK_INTERVAL, default86400—
раз в сутки) — периодичность перепроверки профиля вensureAvatar, сек (0 — обновлять только при пустом аватаре). max_users: колонкиphone,email,profile_checked_at(nullable timestamp синхронизации профиля).
Изменение (BC)
- Реестр чатов переименован: класс
BotChat→MaxChat(таблицаmax_chats), enum
ChatStatus→MaxChatStatus, слушательPersistChatListener→PersistMaxChatListener, связь
MaxUser::chats()→MaxUser::maxChats(). Уже задеплоенным БД нужна отдельная миграция на rename. - Публичный контракт конфигурации сохранён: ключи
chats.*, envMAX_CHATS_*,Support\Config::chatsModel()не
менялись. Модели переопределяются черезMAX_CHATS_MODEL/MAX_USERS_MODEL.
Затронутые сценарии
- Профиль с аватаром подтягивается «по требованию»:
RefreshDatabase-совместимый вызовrefresh()/ensureAvatar()
из приложения после того, как бот добавлен в чат. Заполнение аватара прямо при апдейтах намеренно НЕ добавлено
(экономим вызовы API под Rate limit) — основной путь по требованию.
Качество
- PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
- PHPUnit зелёные (166 тестов / 397 assertions); покрытие строк 99.48%, порог ≥ 95%.
composer audit— без уязвимостей.
v1.0.9
v1.0.9 — last_activity_at в max_bot_chats
Новое
last_activity_atвmax_bot_chats: nullable timestamp последней активности в чате, обновляется при каждом
апдейте (bot_added/bot_started/bot_stopped/bot_removed). Удобно для сортировки диалогов по свежести.
Качество
- PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
- PHPUnit зелёные (151 тест / 343 assertions); покрытие строк 99.80%, порог ≥ 95%.
composer audit— без уязвимостей.
v1.0.8
v1.0.8 — Реестр пользователей max_users, max_bot_chats
Новое
- Реестр пользователей (
max_users): новая миграция и модельMaxUserс полным набором полей
UserWithPhoto—user_id(PK),first_name,last_name,username,is_bot,last_activity_time,
name,description,avatar_url,full_avatar_url. Все колонки с->comment()на русском. - Listener
PersistBotChatListenerприchats.enabled=trueавтоматически upsert запись вmax_users
при полученииbot_added/bot_started/bot_stopped/bot_removedс объектомuser. - Связь
BotChat → MaxUserчерезuser_id(EloquentbelongsTo, без FK-constraint — гибко при отсутствии
пользователя вmax_users). - Конфиг: новая секция
users.model(envMAX_USERS_MODEL) для переопределения моделиMaxUser.
Изменение (BC)
- Таблица
bot_chatsпереименована вmax_bot_chats. Потребителям необходимо выполнить
php artisan vendor:publish --tag=laravel-max-client-migrations --forceиphp artisan migrate. - Миграции переименованы в
0001_01_01_00000x(Laravel publishable convention) — повторная публикация перезапишет
старые файлы миграций.
Затронутые сценарии
- При добавлении/запуске бота в чат — пользователь автоматически сохраняется в
max_usersс данными из MAX API. Поля
description/avatar_url/full_avatar_urlзаполняются при наличииUserWithPhoto
(webhook отдаётUser— поля доступны для заполнения через APIgetChatMembers/getBotMembership). - Связь
BotChat::maxUserработает напрямую поuser_id— без дополнительных join или подзапросов.
Качество
- PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
- PHPUnit зелёные (149 тестов / 340 assertions); покрытие строк 99.79%, порог ≥ 95%.
composer audit— без уязвимостей.
v1.0.7
v1.0.7 — Ядро ^1.0.6: глобальный rate limit 30 rps, sendAnswer без message, deprecated-права админов, алиас max.log
Зависимости
- Ядро
geekcodev/max-php-clientподнято до^1.0.6. В v1.0.6 ядра:ApiClient::create()получил опциюglobal_rate_limiter(по умолчаниюRateLimiter(30, 30)) — глобальный
предохранитель 30 req/s на весь API, применяется к каждому запросу (включая ретраи и загрузку медиа), при
исчерпании ядро ждёт пополнения, а не бросает исключение;sendAnswer()безmessageшлёт тело{}вместо пустого тела (раньше API отвечал400 Empty request body);ChatAdminPermissionполучил deprecated-значенияpost_edit_delete_message/edit_message/delete_message—
теперьgetBotMembership()/getChatAdmins()/getChatMembers()парсят чаты со старыми правами;addChatAdmin()
выдачу таких прав отклоняет (InvalidArgumentException) и получил параметр?int $marker;getChatAdmins()больше не шлёт querymarker/count(их нет в спеке; параметры deprecated и игнорируются);join_time— миллисекунды (документировано в спеке).
Новое
- Глобальный rate limit выведен в конфиг адаптера: блок
global_rate_limit(envMAX_GLOBAL_RATE_LIMIT_*, дефолт
30/30 rps).MaxServiceProviderпередаёт его вApiClient::create(globalRateLimiter: ...). - PHPDoc фасада
Maxсинхронизирован с ядром v1.0.6:addChatAdmin(..., ?int $marker = null).
Изменение (BC)
- Алиас middleware логирования переименован
max_bot.log→max.log(единый нейминг сmax.webapp/max.csp).
Потребителям, использовавшимmax_bot.logв middleware роутов, нужно обновить роуты наmax.log. Класс
LogMaxRequestsMiddlewareи конфиг/MAX_LOGGING_*не менялись.
Документация
README.md(Требования ^1.0.6, таблица envMAX_GLOBAL_RATE_LIMIT_*, сигнатуры фасада),AGENTS.md(constraints,
факт 30 rps,global_rate_limit.*) и.env.exampleсинхронизированы с ядром v1.0.6 и новым алиасомmax.log.UPGRADE_PLAN.mdобновлён (состояние, история прецедентов v1.0.6).
Затронутые сценарии
- Ответ на callback без обновления сообщения (кнопка обработана, меню не меняется):
sendAnswerтеперь работает
(200вместо400 Empty request body) — ядро, правки в адаптере не требовались. - Чтение администраторов/участников на чатах со старыми правами: больше не падает с
InvalidResponseException. - Интенсивная работа с API: глобальный предохранитель 30 rps защищает от rate limit независимо от числа диалогов.
Качество
- PHP 8.4, PSR-12, PHPStan level max — 0 ошибок.
- PHPUnit зелёные (140 тестов / 314 assertions); покрытие строк ≥95%.
composer audit— без уязвимостей.