This guide helps you migrate from the array-based workflow configuration to the new fluent API with PHP 8.3+ features.
- Fluent API: Array-based configuration is deprecated in favor of the new
WorkflowBuilder - Type Safety: All classes now use readonly properties and strong typing
- Namespace Changes: Some classes have moved to new namespaces
- Context Structure:
WorkflowContextis now immutable and readonly
composer update solution-forest/workflow-engine-laravelphp artisan vendor:publish --tag="workflow-engine-config" --forcephp artisan vendor:publish --tag="workflow-engine-migrations"
php artisan migrateBefore (v1.x - Array Configuration):
$orderWorkflow = [
'name' => 'order-processing',
'steps' => [
['id' => 'validate', 'action' => ValidateOrderAction::class],
['id' => 'payment', 'action' => ProcessPaymentAction::class],
['id' => 'shipping', 'action' => CreateShipmentAction::class],
],
'transitions' => [
['from' => 'validate', 'to' => 'payment'],
['from' => 'payment', 'to' => 'shipping'],
],
'error_handling' => [
'payment_failure' => [
'retry_attempts' => 3,
'compensation' => RefundAction::class,
],
],
];
$instance = WorkflowMastery::start($orderWorkflow, $data);After (v2.x - Fluent API):
use SolutionForest\WorkflowEngine\Core\WorkflowBuilder;
use SolutionForest\WorkflowEngine\Core\WorkflowEngine;
$definition = WorkflowBuilder::create('order-processing')
->addStep('validate', ValidateOrderAction::class)
->addStep('payment', ProcessPaymentAction::class, [], null, 3) // 3 retry attempts
->addStep('shipping', CreateShipmentAction::class)
->build();
$engine = app(WorkflowEngine::class);
$instanceId = $engine->start('order-001', $definition->toArray(), $data);Before (v1.x):
class ProcessPaymentAction
{
public function execute($context)
{
$orderData = $context['order'] ?? [];
// Process payment
$result = $this->chargeCard($orderData);
return [
'success' => $result['success'],
'data' => $result['data'] ?? [],
];
}
}After (v2.x):
use SolutionForest\WorkflowEngine\Contracts\WorkflowAction;
use SolutionForest\WorkflowEngine\Core\{WorkflowContext, ActionResult};
class ProcessPaymentAction implements WorkflowAction
{
public function execute(WorkflowContext $context): ActionResult
{
$order = $context->getData('order');
// Process payment
$result = $this->chargeCard($order);
if ($result['success']) {
return ActionResult::success($result['data']);
}
return ActionResult::failure('Payment failed', [
'error' => $result['error'] ?? 'Unknown error'
]);
}
public function canExecute(WorkflowContext $context): bool
{
return $context->hasData('order');
}
public function getName(): string
{
return 'Process Payment';
}
public function getDescription(): string
{
return 'Processes customer payment through configured gateway';
}
}Before (v1.x):
// Context was a simple array
$context = [
'user' => $user,
'order' => $order,
'step_results' => []
];
// Access data directly
$userId = $context['user']['id'];
$orderTotal = $context['order']['total'];After (v2.x):
// Context is now a readonly class
$context = new WorkflowContext(
workflowId: $workflowId,
stepId: $stepId,
data: [
'user' => $user,
'order' => $order
]
);
// Use getter methods with dot notation
$user = $context->getData('user');
$userId = $context->getData('user.id');
$orderTotal = $context->getData('order.total');
// Create new context with additional data (immutable)
$newContext = $context->with('payment_result', $paymentData);
// Or merge multiple values
$newContext = $context->withData(['payment_result' => $paymentData, 'status' => 'paid']);Before (v1.x):
try {
$result = $action->execute($context);
if (!$result['success']) {
// Handle error
$this->handleError($result['error']);
}
} catch (\Exception $e) {
// Log error
Log::error('Workflow failed', ['error' => $e->getMessage()]);
}After (v2.x):
try {
$result = $action->execute($context);
if ($result->isSuccess()) {
$this->proceedToNextStep($result);
} else {
$this->handleFailure($result);
Log::warning('Action failed', [
'error' => $result->getErrorMessage(),
'metadata' => $result->getMetadata()
]);
}
} catch (\Exception $e) {
Log::error('Workflow failed', [
'workflow_id' => $context->workflowId,
'step_id' => $context->stepId,
'error' => $e->getMessage()
]);
}use SolutionForest\WorkflowEngine\Core\WorkflowState;
// Rich enum with methods
$state = WorkflowState::RUNNING;
echo $state->label(); // "Running"
echo $state->color(); // "blue"
echo $state->icon(); // "▶️"
echo $state->description(); // Detailed description
// State category checks
$state->isActive(); // true for PENDING, RUNNING, WAITING, PAUSED
$state->isFinished(); // true for COMPLETED, FAILED, CANCELLED
$state->isSuccessful(); // true only for COMPLETED
$state->isError(); // true only for FAILED
// Smart state transitions
if ($state->canTransitionTo(WorkflowState::COMPLETED)) {
$workflow->complete();
}
$validTargets = $state->getValidTransitions(); // Array of valid target statesuse SolutionForest\WorkflowEngine\Attributes\{WorkflowStep, Timeout, Retry, Condition};
#[WorkflowStep(
id: 'process-payment',
name: 'Process Payment',
description: 'Processes customer payment'
)]
#[Timeout(minutes: 5)]
#[Retry(attempts: 3, backoff: 'exponential')]
#[Condition('order.amount > 0')]
class ProcessPaymentAction implements WorkflowAction
{
public function execute(WorkflowContext $context): ActionResult
{
// Action implementation
}
public function canExecute(WorkflowContext $context): bool
{
return $context->hasData('order');
}
public function getName(): string
{
return 'Process Payment';
}
public function getDescription(): string
{
return 'Processes customer payment';
}
}use SolutionForest\WorkflowEngine\Core\WorkflowBuilder;
// Pre-built workflow templates
$onboarding = WorkflowBuilder::quick()
->userOnboarding('premium-onboarding')
->then(SetupPremiumFeaturesAction::class)
->build();
$orderFlow = WorkflowBuilder::quick()
->orderProcessing('express-order')
->build();
$approval = WorkflowBuilder::quick()
->documentApproval('legal-review')
->build();// HTTP actions with template processing
$workflow = WorkflowBuilder::create('api-integration')
->http('https://api.example.com/webhook', 'POST', [
'user_id' => '{{ user.id }}',
'event' => 'user_registered',
])
->build();
// Conditional actions
$workflow = WorkflowBuilder::create('conditional-flow')
->when('user.age >= 18', fn($builder) =>
$builder->addStep('adult-verification', AdultVerificationAction::class)
)
->build();Error:
TypeError: Argument 1 passed to WorkflowContext::__construct() must be of type array
Solution: Use named parameters with the WorkflowContext constructor:
// Wrong
$context = new WorkflowContext($user, $workflowId, $stepId);
// Correct
$context = new WorkflowContext(
workflowId: $workflowId,
stepId: $stepId,
data: ['user' => $user]
);Error:
Error: Cannot modify readonly property WorkflowContext::$data
Solution: Use immutable methods instead of direct property modification:
// Wrong
$context->data['new_key'] = $value;
// Correct - single value
$context = $context->with('new_key', $value);
// Correct - multiple values
$context = $context->withData(['new_key' => $value, 'other' => $other]);Error:
Class 'SolutionForest\WorkflowMastery\...' not found
Solution: Update your use statements:
// Old namespace
use SolutionForest\WorkflowMastery\Contracts\WorkflowAction;
// New namespace
use SolutionForest\WorkflowEngine\Contracts\WorkflowAction;Error:
Call to undefined method ActionResult::retry()
Solution: Use the new ActionResult API:
// Old API
return ActionResult::retry('Rate limited');
$result->success; // property access
$result->message; // property access
// New API
return ActionResult::failure('Rate limited', ['retry_after' => 60]);
$result->isSuccess(); // method call
$result->getErrorMessage(); // method call
$result->getData(); // method call
$result->getMetadata(); // method callThe new version includes several performance optimizations:
- 70% less code for common workflow patterns
- Faster execution with readonly properties and optimized data structures
- Better memory usage with immutable contexts
- Improved caching of workflow definitions
If you encounter issues during migration:
- Review the API Reference
- Check the Advanced Features guide
- Open an issue on GitHub