Automation Plugin

Jira-style automation engine for Festi projects. Admins manage rules in a DGS grid at /automation/workspace/ (search, enable/disable toggle, pagination) and compose each Trigger → Conditions → Actions rule on a Drawflow canvas at /automation/workspace/rules/<id>/ (or rules/create/ for a new one). Triggers and actions are added through a searchable picker dialog grouped by owning plugin; the registry is fetched via AJAX, so the builder scales to hundreds of registry entries. The available building blocks are discovered by scanning PHP attributes declared in the plugins that own them, so there is no central registry to maintain. The read-only trigger registry lives at /automation/workspace/triggers/.

Requirements

  • The Festi Theme package (Festi\Theme\Plugin\DisplayPlugin).
  • The Modules plugin. The plugin class implements plugin\Modules\domain\scope\ISystemSpaceModule.
  • The Queues plugin. Each trigger event goes into the queue Automation.

Installation

Use the Festi CLI to add the plugin to the project. Run the command from the project root:

./vendor/bin/festi-plugin --mode install --path ./src/dashboard/ --dump ./dumps/ \
    --plugin Automation --repository [email protected]:FestiPlugins/php_festi_plugin_automation.git

This example was not run for this page.

The command adds the repository to the plugins directory as a git submodule and adds the plugin to festi_plugins. The repository has no install folder. Thus the command does not create the automation_* tables, the queue table, the URL rules or the sections. Add them with a migration of the project and apply it with festi-migrate.

The plugin uses these sections (AutomationSections):

Section Constant Function
automation_settings SETTINGS The rule list, the rule builder and the rule log.
automation_manage_triggers MANAGE_TRIGGERS The trigger registry page.
automation_user USER The user scope of the rules.

Declaring a trigger

A trigger is a FestiEvent subclass tagged with #[AutomationTrigger]. It must define a non-empty EVENT_TYPE constant — that constant is the registry ident and the event type the engine subscribes to. Place trigger classes in plugins/<Name>/domain/automation/trigger/ (or Domain/Automation/Trigger/ for PSR-4 plugins) so the scanner finds them.

#[AutomationTrigger(
    caption: 'User Created',
    description: 'Fires when a new employee account is created.'
)]
class UserCreatedEvent extends FestiEvent
{
    public const EVENT_TYPE = 'user_created_event';

    #[AutomationConditionEntity(caption: 'User')]
    protected UserValuesObject $user;
}
  • #[AutomationConditionEntity] marks an event property holding a values object the rules can inspect and pass to actions.
  • #[AutomationConditionField] marks a scalar public event property as a testable field.

Declaring condition fields on a values object

Tag getters of the values object with #[AutomationConditionField]. The field ident defaults to the getter name (getTypeID → type_id); the getter must declare a scalar return type.

#[AutomationConditionField(name: 'email', caption: 'Email')]
public function getEmail(): ?string

Declaring an action

Tag a public plugin method with #[AutomationAction]. Scalar parameters tagged #[AutomationActionOption] are filled by the rule author in the builder; values-object parameters tagged #[AutomationActionEntity] are bound to a trigger entity at run time. Pass multiline: true on an option to render it as a textarea instead of a single-line input.

#[AutomationAction(caption: 'Notify User')]
public function sendUserNotification(
    #[AutomationActionEntity(caption: 'User')]
    UserValuesObject $userValuesObject,
    #[AutomationActionOption(caption: 'Title')]
    string $title,
    #[AutomationActionOption(caption: 'Message', multiline: true)]
string $message
): void

Action options can also use a typed dynamic options provider. Providers implement IActionOptionsProvider and return stored values mapped to their user-facing captions. Set multiple: true on an array parameter to render a multi-select and persist all selected values.

#[AutomationActionOption(
    caption: 'Roles',
    optionsProvider: UserTypeOptionsProvider::class,
    multiple: true
)]
array $userTypeIDs

Notification recipients

The Notification plugin exposes three dashboard-notification actions:

  • Notify User sends to the user entity emitted by the trigger.
  • Notify User's Mentors resolves that user's active mentors when the queued action runs.
  • Notify Users by Role resolves all active users of one or more selected company roles when the queued action runs.

Recipient groups are resolved at action execution time. Empty groups are a successful no-op, and recipients are deduplicated within each action.

Registry synchronization

The scanner (Domain/Scanner/AutomationScanner) reflects the tagged classes and Domain/Scanner/DefinitionSynchronizer upserts them into the automation_* tables. The method AutomationPlugin::onCronSyncPlugins() does the scan. Start it from a cron script of the host project or with the Festi CLI:

./vendor/bin/festi-plugin --mode exec --plugin Automation --method onCronSyncPlugins

This example was not run for this page.

Broken classes are skipped; one bad file never aborts the sync. When the scan has errors, the synchronizer does not remove registry rows.

Runtime

AutomationPlugin::onLoadSystemSpaceModule() subscribes to every trigger ident that has an active rule. The Modules plugin calls this method when it loads the module. The plugin has no init.php. A fired event is serialized (Domain/Engine/TriggerContextSerializer) and enqueued through the Queues plugin (queue_automation table). The queue worker rehydrates the context, evaluates each active rule's conditions (Domain/Engine/ConditionEvaluator, AND semantics, missing data = no match) and executes matching actions (Domain/Engine/ActionInvoker) with per-action error isolation.

Run the worker: php plugins/Queues/queues.php --name Automation.

Known Spike limitations

  • Condition operands are typed literals; enum operands (e.g. picking a user type by name instead of its numeric id) are a follow-up.
  • Rules are linear: one trigger, N AND-joined conditions, M actions.
  • Removing a trigger/action from code deletes its registry rows on the next sync; dependent rules are cascade-deleted rather than soft-flagged.