Actions
Action — PHP-класс, привязанный к блоку. Имеет две роли:
- Источник данных для блока. Action выполняется автоматически перед рендером блока, возвращает массив, который становится доступен в Blade-шаблоне как переменная
$actions. - HTTP-обработчик. Action можно вызвать через POST/GET
/action/{slug}— для submit форм, webhook'ов, cross-domain интеграций.
Контракт BlockActionInterface
namespace Templite\Cms\Contracts;
interface BlockActionInterface
{
public function params(): array;
public function returns(): array;
public function handle(array $params, ActionContext $context): array;
public function csrfEnabled(): bool;
}params(): array
Описание входных параметров. Используется для:
- генерации UI настроек action в админке (при привязке к блоку);
- валидации параметров;
- описания в MCP-протоколе.
Формат:
public function params(): array
{
return [
'limit' => [
'type' => 'number',
'label' => 'Количество',
'default' => 6,
],
'page_type' => [
'type' => 'select',
'label' => 'Тип страницы',
'options' => 'page_types',
'required' => true,
],
];
}returns(): array
Описание возвращаемых данных. Используется для:
- подсказок доступных переменных в редакторе кода блока;
- документации в MCP-протоколе;
- автодополнения в CodeMirror.
public function returns(): array
{
return [
'pages' => [
'type' => 'Collection<Page>',
'description' => 'Коллекция страниц',
],
'total' => [
'type' => 'int',
'description' => 'Общее количество',
],
];
}handle(array $params, ActionContext $context): array
Выполнение action. Возвращает данные для шаблона блока.
$params — параметры, заданные при привязке к блоку (с учётом page-level override). $context — объект ActionContext с полным контекстом запроса.
csrfEnabled(): bool
Включает CSRF-проверку при HTTP-вызове через /action/{slug}. По умолчанию обычно true. Отключайте для webhook'ов и cross-domain fetch — при выключенном CSRF действует throttle + honeypot.
ActionContext
Объект-контейнер, передаваемый в handle() вторым аргументом:
namespace Templite\Cms\Contracts;
class ActionContext
{
public function __construct(
public Page $page, // Текущая страница
public Request $request, // HTTP-запрос
public array $global, // Глобальные настройки (key => value)
public array $blockData, // Resolved-значения полей текущего блока
) {}
}В $page — модель страницы со всеми связями. В $request — query/post-параметры (?page=2, данные формы). В $global — массив значений глобальных настроек. В $blockData — значения полей блока, уже прошедшие через BlockDataResolver.
Структура Action
В БД (таблица actions)
| Колонка | Назначение |
|---|---|
slug | Уникальный идентификатор |
name | Отображаемое название |
class_name | Полный namespace класса (для файловых actions из app/) |
source | database / file / vendor |
params | JSON со схемой параметров (зеркало params()) |
returns | JSON со схемой возврата (зеркало returns()) |
description | Описание для UI |
code_hash | SHA-256 хеш файла кода — для контроля целостности |
screen | FK на files — скриншот для UI |
allow_http | Boolean — разрешать HTTP-вызовы через /action/{slug} |
На диске
Действие — это один PHP-файл с классом-имплементацией BlockActionInterface:
app/Actions/<ClassName>.php— с PHP namespacestorage/cms/actions/<ClassName>.php— без namespace (загружается как standalone)
Источники (ActionRegistry)
Реестр поддерживает три источника с приоритетом app > storage > vendor.
| Источник | Путь | Формат |
|---|---|---|
app | app/Actions/<ClassName>.php | PHP-класс с namespace App\Actions |
storage | storage/cms/actions/<ClassName>.php | PHP-файл без namespace |
vendor | Регистрация через ServiceProvider пакета | Любой PHP-класс |
Пример
PHP-класс app/Actions/LatestArticles.php:
<?php
namespace App\Actions;
use Templite\Cms\Contracts\ActionContext;
use Templite\Cms\Contracts\BlockActionInterface;
use Templite\Cms\Models\Page;
class LatestArticles implements BlockActionInterface
{
public function params(): array
{
return [
'limit' => [
'type' => 'number',
'label' => 'Количество статей',
'default' => 6,
],
];
}
public function returns(): array
{
return [
'articles' => [
'type' => 'Collection<Page>',
'description' => 'Список свежих статей',
],
];
}
public function handle(array $params, ActionContext $context): array
{
$limit = (int) ($params['limit'] ?? 6);
return [
'articles' => Page::published()
->whereHas('pageType', fn ($q) => $q->where('slug', 'article'))
->latest()
->take($limit)
->get(),
];
}
public function csrfEnabled(): bool
{
return true;
}
}В шаблоне блока (storage/cms/blocks/<slug>/template.blade.php):
<section class="latest">
@foreach ($actions['articles'] ?? [] as $article)
<article>
<a href="{{ $article->url }}">
<h3>{{ $article->title }}</h3>
</a>
</article>
@endforeach
</section>После привязки action LatestArticles к блоку и установки limit = 6 через UI — переменная $actions['articles'] появится в шаблоне с коллекцией свежих статей.
Привязка action к блоку
Связь хранится в таблице block_actions:
| Колонка | Назначение |
|---|---|
block_id | FK на блок |
action_id | FK на action |
params | JSON со значениями параметров (с учётом схемы из params()) |
order | Порядок выполнения |
Дополнительно — page_blocks.action_params (JSON) для page-level override параметров: один и тот же блок на разных страницах может вызывать action с разными параметрами.
В UI: страница блока → вкладка «Actions» → выбрать action → задать параметры.
HTTP-вызовы
Templite регистрирует роут /action/{actionSlug} (GET и POST), который позволяет вызвать action напрямую — без рендера блока.
Используется для:
- submit форм (через
<form action="/action/send-contact-form" method="POST">); - webhook'ов от внешних сервисов;
- AJAX-вызовов из JS на сайте.
Защита:
- CSRF — управляется методом
csrfEnabled(). По умолчанию обычно включён для POST. - Throttle — стандартный Laravel rate limiter.
- Honeypot — встроенный антибот-механизм (есть компонент
<x-cms::honeypot>для встраивания в формы). allow_http— общая разрешённость HTTP-вызова на уровне записиactions. Еслиfalse—/action/{slug}вернёт 404.
Также есть отдельный роут /cms/action/{blockSlug} для admin-операций со страницами блоков.
Защита целостности кода
ActionCodeValidator проверяет код при каждой загрузке:
- Хеш файла. Текущий SHA-256 файла сравнивается с
actions.code_hashв БД. Если файл изменён вне CMS —ActionRegistry::resolve()возвращаетnull, нарушение логируется вsecurity-канал. - Токенизация. Запрещённые PHP-конструкции (например,
eval, прямойsystem()/exec()) отлавливаются ещё до загрузки класса.
Это защита от подмены файлов на диске между деплоями.
Создание action
Через UI
- Меню Actions → Создать.
- Указать slug, имя, описание.
- Описать
paramsиreturnsчерез UI-редактор схемы. - Написать PHP-код в встроенном редакторе кода (CodeMirror с PHP-подсветкой).
- Сохранить.
Файл попадает в storage/cms/actions/<ClassName>.php. code_hash рассчитывается автоматически.
Через cms:make-action
docker exec templite-app php artisan cms:make-action LatestArticles --storageС флагом --storage создаёт storage/cms/actions/LatestArticles.php — runtime найдёт.
Без --storage команда создаёт app/Cms/Actions/LatestArticles.php — ActionRegistry сканирует app/Actions/, не app/Cms/Actions/. Файл нужно перенести либо использовать --storage.
Через файловую систему
Рекомендованный способ для кода под Git: положить app/Actions/<ClassName>.php (с namespace App\Actions) — ActionRegistry::scanAppActions() подхватит автоматически.
Дополнительно нужна запись в БД (через UI «Actions» или MCP create_action) — она сопоставляет slug со class_name и хранит params/returns/description.
Импорт и экспорт
Action экспортируется в ZIP вместе с PHP-файлом кода:
- JSON-описание (slug, name, params, returns, description, allow_http)
<slug>.php— файл с PHP-классом
При импорте Action::fromImportArray() создаёт запись, importCodeFromZip() восстанавливает файл в storage/cms/actions/.
Подводные камни
Подводные камни
- Action выполняется синхронно перед каждым рендером блока (с учётом кэша). Тяжёлые запросы/обращения к внешним API будут тормозить страницу. Для долгих задач — Laravel Queue:
dispatch(new HeavyJob(...))внутриhandle(), а результат сохранять отдельно и читать из кэша/БД. - Результат action кэшируется вместе с HTML блока (если
page_blocks.cache_enabled = true). Если данные часто меняются — отключите кэш блока на странице. - Файл вне CMS не загрузится, если
code_hashв БД не совпадает с реальным. После правки файла через IDE — обновите хеш через UI («Сохранить» в админке) или MCPupdate_action. cms:make-actionбез--storageсоздаётapp/Cms/Actions/<ClassName>.php— runtime не найдёт. Используйте--storageили кладите файл вручную вapp/Actions/.- HTTP-вызов
/action/{slug}требуетallow_http = trueв БД, иначе 404. Дополнительно методcsrfEnabled()контролирует CSRF-проверку. - Параметры action имеют три уровня переопределения: дефолты из
params()→ значения при привязке к блоку (block_actions.params) → page-level override (page_blocks.action_params). При выполнении они мержатся именно в этом порядке. - В шаблоне блока используйте
$actions['key'] ?? default— если action не выполнился (например, ошибка вhandle()), ключа в$actionsможет не быть. В отличие от$fields, переменная$actions— обычный массив, неFieldsBag.