Поддержка

Глобальные

Действия, доступные вне контекста ресурса — из кода и через поиск Slim

Глобальные действия работают вне контекста конкретного ресурса. Они доступны:

  • Программно — из любого места приложения через метод dispatch()
  • Через Slim — в глобальном поиске, пользователь находит и запускает действие

Регистрация

Глобальные действия регистрируются в сервис-провайдере через Frame::registerGlobalActions():

app/Providers/FrameServiceProvider.php
use Frame\Core\Frame;
use App\Frame\Actions\SyncPricesAction;
use App\Frame\Actions\ClearCacheAction;

public function boot(): void
{
    parent::boot();

    Frame::registerGlobalActions([
        SyncPricesAction::class,
        ClearCacheAction::class,
    ]);
}

Можно передавать класс (будет разрешён через контейнер), экземпляр или callable:

Frame::registerGlobalActions([
    // Класс — разрешается через app()
    SyncPricesAction::class,

    // Экземпляр
    Action::make('Очистить кэш', 'Удалить все закэшированные данные')
        ->icon('tabler-trash')
        ->action(fn () => Artisan::call('cache:clear')
            ? Action::message('Кэш очищен')
            : Action::error('Ошибка')),
]);

Создание класса действия

Глобальное действие — обычный класс Action. Отличие от ресурсных: метод handle() получает пустую коллекцию моделей.

app/Frame/Actions/SyncPricesAction.php
class SyncPricesAction extends Action
{
    public ?string $title = 'Синхронизировать цены';

    public ?string $description = 'Запустить синхронизацию цен из внешнего API';

    public ?string $icon = 'tabler-refresh';

    public bool $confirmed = true;

    public function fields(): array
    {
        return [
            Select::make('currency', 'Валюта')
                ->options(['USD' => 'USD', 'EUR' => 'EUR', 'RUB' => 'RUB'])
                ->rules('required'),
        ];
    }

    public function handle(LazyCollection $models, ActionFields $fields): ?ActionResult
    {
        $count = PriceSync::run($fields->get('currency'));

        return static::message("Синхронизировано {$count} цен");
    }
}

Многошаговые действия (мастер)

Форму действия можно разбить на шаги — они отображаются как мастер (wizard). Объявите в классе метод steps(), возвращающий список шагов; каждый шаг — массив ['label' => ..., 'fields' => [...], 'description' => ...] (description опционально). Отдельный fields() при этом не нужен: поля собираются со всех шагов, валидация тоже общая.

app/Frame/Actions/OnboardUserAction.php
class OnboardUserAction extends Action
{
    public ?string $title = 'Онбординг';

    public function steps(): array
    {
        return [
            [
                'label' => 'Профиль',
                'fields' => [
                    Text::make('name', 'Имя')->required(),
                ],
            ],
            [
                'label' => 'Доступ',
                'description' => 'Учётные данные',
                'fields' => [
                    Text::make('email', 'Email')->required(),
                    Password::make('password', 'Пароль'),
                ],
            ],
        ];
    }

    public function handle(LazyCollection $models, ActionFields $fields): ?ActionResult
    {
        return static::message('Пользователь создан: ' . $fields->get('name'));
    }
}
Шаг описывается обычным массивом — отдельного класса Step нет. Поддерживаются ключи label, fields и опциональный description; условий видимости на уровне шага нет.

Программный вызов

Метод dispatch() позволяет выполнить действие из кода, минуя HTTP-слой:

// Без параметров
SyncPricesAction::make()->dispatch();

// С полями
SyncPricesAction::make()->dispatch(['currency' => 'USD']);

// С моделями
PublishAction::make()->dispatch(
    fields: ['notify' => true],
    models: Post::where('draft', true)->lazy(),
);

dispatch() оборачивает выполнение в транзакцию и записывает событие в журнал.

Отображение в Slim

Зарегистрированные глобальные действия автоматически появляются в секции «Действия» поиска Slim. Поддерживается:

  • Нечёткий поиск по названию
  • Авторизация — действие видно только пользователям с правами (через ->can())
  • Подтверждение — модальное окно с кнопками подтверждения/отмены
  • Поля формы — если действие определяет fields(), они отображаются в модальном окне
  • Валидация — ошибки 422 показываются без закрытия модального окна

Авторизация

Ограничение доступа работает через метод can():

Frame::registerGlobalActions([
    SyncPricesAction::make()->can('admin'),

    Action::make('Опасное действие')
        ->can(fn (FrameRequest $request) => $request->user()->isOwner()),
]);

Действия, не прошедшие проверку, не отображаются в Slim и возвращают 403 при попытке вызова.

Деструктивные действия

Для опасных операций используйте DestructiveAction:

class PurgeDataAction extends DestructiveAction
{
    public ?string $title = 'Очистить данные';
    // ...
}

Деструктивные действия всегда требуют подтверждения и отображаются с красной кнопкой.

Именование

Глобальные действия автоматически получают имя вида global::slug-from-title. Например, действие с $title = 'Sync Prices' будет иметь имя global::sync-prices.

Результаты

Глобальные действия поддерживают все стандартные результаты:

return static::message('Операция выполнена');
return static::error('Что-то пошло не так');
return static::redirect('/reports/latest');
return static::push(['name' => 'dashboards', 'params' => ['name' => 'main']]);