Custom Step Types
Create your own step types for domain-specific logic by implementing the StepType interface.
StepType Interface
php
interface StepType
{
public static function getName(): string;
public function execute(array $definition, ExecutionContext $context): StepResult;
public function validate(array $definition): void;
}| Method | Purpose |
|---|---|
getName() | Returns the type name used in definitions ('send_email') |
execute() | Performs the step logic, returns a StepResult |
validate() | Validates the step definition at workflow creation time |
1. Create the Step Type
php
namespace App\StepTypes;
use Workflowable\Workflowable\Contracts\StepType;
use Workflowable\Workflowable\Engine\ExecutionContext;
use Workflowable\Workflowable\Engine\StepResult;
class SendEmailStepType implements StepType
{
public static function getName(): string
{
return 'send_email';
}
public function execute(array $definition, ExecutionContext $context): StepResult
{
try {
$to = $context->getVariable($definition['to_variable'] ?? 'email');
$template = $definition['template'];
Mail::to($to)->send(new DynamicMail($template, $context->getVariables()));
return StepResult::success(['email_sent' => true, 'sent_to' => $to]);
} catch (\Throwable $e) {
return StepResult::failed($e->getMessage());
}
}
public function validate(array $definition): void
{
if (empty($definition['template'])) {
throw new \InvalidArgumentException('Send email step requires a "template" field.');
}
}
}2. Register the Step Type
In a service provider:
php
use Workflowable\Workflowable\Registries\StepTypeRegistry;
public function boot(): void
{
app(StepTypeRegistry::class)->register('send_email', \App\StepTypes\SendEmailStepType::class);
}3. Use in Workflow Definitions
php
'steps' => [
'notify_customer' => [
'type' => 'send_email',
'template' => 'order-confirmation',
'to_variable' => 'customerEmail',
],
],
'transitions' => [
'notify_customer' => 'completed',
]StepResult
php
// Step completed successfully - data merged into workflow variables
StepResult::success(['key' => 'value']);
// Step failed
StepResult::failed('Payment gateway timeout');
// Step is waiting (e.g., for an external callback or async result)
StepResult::waiting();Custom Action Handlers vs Custom Step Types
For simpler cases where you just need to run some logic, register an action handler instead of a full step type:
php
// In config/workflowable.php
'actions' => [
'send_slack_notification' => \App\Actions\SendSlackNotification::class,
],php
class SendSlackNotification
{
public function handle(ExecutionContext $context): array
{
$message = $context->getVariable('notification_message');
Slack::send($message);
return ['slack_sent' => true];
}
}Action handlers are used with the built-in action step type and don't require implementing the StepType interface. Use a custom step type when you need:
- Custom validation logic
- A different execution model (e.g., waiting, webhooks)
- Reusable behavior across multiple workflows with type-specific configuration