# Взаимодействия ← [Назад к разделу «Модули»](README.md) Модуль `interactions` хранит унифицированные действия посетителей: оценки, голоса, реакции и выбор вариантов. Он не выводит самостоятельный виджет и не имеет вставочного тега. Пользовательский интерфейс предоставляют зависимые модули, например **Рейтинги**, **Комментарии** и **Опросы**. ## Модель данных Каждое действие определяется четырьмя частями: | Часть | Пример | Назначение | | --- | --- | --- | | Тип объекта | `document`, `comment`, `poll` | К какому классу сущностей относится действие. | | Ключ объекта | `145` | ID или другой стабильный строковый ключ сущности. | | Канал | `rating`, `comment_vote`, `poll_vote` | Набор правил и разрешённых действий. | | Действие | `rating`, `up`, `option:8` | Конкретный выбор пользователя внутри канала. | Перед любой записью модуль проверяет существование прикладного объекта. Тип `document` поддерживается самим runtime: удалённый или отсутствующий документ получит `404`. Типы `comment`, `poll` и `gallery` регистрируют соответствующие модули. Для собственного типа нужен resolver `interactions.target.resolve`; одной проверки формата ключа или policy канала недостаточно. ## Режимы каналов | Режим | Поведение | | --- | --- | | `selection` | У пользователя остаётся один вариант. Повторный `toggle` может снять выбор. | | `multiple` | Одновременно хранится несколько вариантов с ограничением `max_selections`. | | `rating` | Хранится одна числовая оценка в диапазоне `min_value`–`max_value`. | Операция `set` создаёт или обновляет действие, `toggle` переключает его, а `remove` удаляет. После изменения агрегаты канала перестраиваются в той же транзакции: публичному коду не нужно считать голоса по сырым записям. ## Публичный API Сводка объекта: ```text GET /api/v1/interactions/document/145 ``` Ответ содержит публичные определения каналов, агрегаты по действиям и выбор текущего посетителя. Новый CSRF-токен возвращается в поле `csrf`. Запись действия: ```text POST /api/v1/interactions/document/145/rating Content-Type: application/json X-CSRF-Token: {"operation":"set","action":"rating","value":5} ``` Для реакции запрос может выглядеть так: ```json {"operation":"toggle","action":"up"} ``` Успешный ответ возвращает `changed`, выполненную операцию, actor и полную новую сводку `snapshot`. Ошибки используют HTTP-коды `401`, `403`, `409`, `419`, `422` и `429`. ## Регистрация своего канала Модуль добавляет канал фильтром `interactions.channels`: ```php Hooks::add('interactions.channels', function ($channels) { $channels['reaction'] = array( 'code' => 'reaction', 'label' => 'Реакция', 'mode' => 'selection', 'actions' => array('like', 'useful'), 'target_types' => array('document'), 'public_read' => true, 'public_write' => true, 'anonymous' => true, 'toggle' => true, 'rate_limit' => 20, 'rate_window' => 60, ); return $channels; }); ``` Код канала и действия допускают латиницу, цифры и служебные разделители. Для динамических действий вместо `actions` можно объявить `action_pattern`. ## Регистрация своего типа объекта Resolver должен явно сообщить, обработал ли он тип, существует ли объект и разрешена ли запись в его текущем состоянии: ```php Hooks::add('interactions.target.resolve', function ($target) { if (!is_array($target) || $target['type'] !== 'article') { return $target; } $article = ArticleRepository::find($target['key']); $target['handled'] = true; $target['exists'] = $article !== null; $target['writable'] = $article && $article['status'] === 'published'; $target['message'] = $article ? 'Материал закрыт для действий' : 'Материал не найден'; return $target; }); ``` Неизвестный тип без resolver-а отклоняется с `422`, отсутствующий объект — с `404`, существующий, но недоступный для записи — с `409`. Resolver не должен создавать объект или изменять его состояние. ## Хуки | Hook | Когда вызывается | Основные данные | | --- | --- | --- | | `interactions.channels` | При сборке реестра каналов | Ассоциативный массив definitions. | | `interactions.target.resolve` | Перед policy записи и созданием внутренней цели | `type`, `key`, `operation`, `handled`, `exists`, `writable`, `message`. | | `interactions.recording` | До rate limit и записи | target, channel, action, value, operation, metadata, actor. | | `interactions.recorded` | После commit и пересчёта агрегатов | changed, operation, actor и snapshot. | В `interactions.recording` можно изменить данные либо выбросить исключение с подходящим HTTP-кодом. Не выполняйте повторную запись в тот же канал из `interactions.recorded`: это создаст рекурсивный цикл. ## Настройки и удаление - **Анонимные действия** разрешают выдавать посетителю устойчивый анонимный actor. - **Действий в минуту** задаёт общий лимит, если канал не объявил свой. - Каждая запись ограничивается по actor. Для гостя дополнительно действует лимит по HMAC-префиксу сети и конкретной цели: IPv4 `/24`, IPv6 `/64`. - **Хеш IP** управляет только сохранением диагностического хеша в записи. Сетевой limiter работает независимо и не хранит исходный IP. Uninstall удаляет цели, действия и агрегаты. Зависимые активные модули сначала нужно отключить или деинсталлировать.