PHP code example of solution-forest / workflow-engine-core

1. Go to this page and download the library: Download solution-forest/workflow-engine-core library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

solution-forest / workflow-engine-core example snippets


use SolutionForest\WorkflowEngine\Core\WorkflowBuilder;
use SolutionForest\WorkflowEngine\Core\WorkflowEngine;
use SolutionForest\WorkflowEngine\Core\WorkflowContext;
use SolutionForest\WorkflowEngine\Core\ActionResult;
use SolutionForest\WorkflowEngine\Actions\BaseAction;

// Define custom actions
class ValidateOrderAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        $orderId = $context->getData('order_id');

        // Your validation logic here
        if ($this->isValidOrder($orderId)) {
            return ActionResult::success(['validated' => true]);
        }

        return ActionResult::failure('Invalid order');
    }
}

// Build a workflow definition
$definition = WorkflowBuilder::create('order-processing')
    ->description('Process customer orders')
    ->addStep('validate', ValidateOrderAction::class)
    ->addStep('payment', ProcessPaymentAction::class, timeout: 300, retryAttempts: 3)
    ->addStep('fulfillment', FulfillOrderAction::class)
    ->build();

// Create engine with a storage adapter (InMemoryStorage ships with the package)
use SolutionForest\WorkflowEngine\Storage\InMemoryStorage;

$engine = new WorkflowEngine(new InMemoryStorage(), $eventDispatcher);

// Start and run the workflow
$instanceId = $engine->start(
    'order-processing',
    $definition->toArray(),
    ['order_id' => 123, 'customer_id' => 456]
);

// Check the result
$instance = $engine->getInstance($instanceId);
echo $instance->getState()->value; // "completed"

use SolutionForest\WorkflowEngine\Core\WorkflowBuilder;

$workflow = WorkflowBuilder::create('order-flow')
    ->description('Process customer orders')
    ->addStep('validate', ValidateOrderAction::class)
    ->when('order.total > 1000', function ($builder) {
        $builder->addStep('fraud_check', FraudCheckAction::class);
    })
    ->addStep('payment', ProcessPaymentAction::class, timeout: 300, retryAttempts: 3)
    // fakeEmail() records an email but does NOT send one - this package ships
    // no mail transport. Use your own action for real delivery.
    ->fakeEmail('order-confirmation', '[email protected]', 'Order Confirmed')
    ->build();

// Quick templates for common patterns
$workflow = WorkflowBuilder::quick()->userOnboarding();
$workflow = WorkflowBuilder::quick()->orderProcessing();
$workflow = WorkflowBuilder::quick()->documentApproval();

use SolutionForest\WorkflowEngine\Attributes\Retry;

#[Retry(attempts: 3, backoff: 'exponential', delay: 1000)]
class ReliableApiAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        // Metadata only - the engine does not read this attribute yet.
        // For real retries use: ->addStep('id', Action::class, retryAttempts: 3)
        return ActionResult::success();
    }
}

use SolutionForest\WorkflowEngine\Attributes\Timeout;

#[Timeout(seconds: 30)]
class TimedAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        // Metadata only - the engine does not read this attribute yet.
        // For a real timeout use: ->addStep('id', Action::class, timeout: 30)
        return ActionResult::success();
    }
}

use SolutionForest\WorkflowEngine\Attributes\Condition;

#[Condition('order.amount > 100')]
class PremiumProcessingAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        // Metadata only - the engine does not read this attribute yet.
        // For real gating use: ->when('order.amount > 100', fn ($b) => ...)
        return ActionResult::success();
    }
}

use SolutionForest\WorkflowEngine\Attributes\WorkflowStep;

#[WorkflowStep(id: 'send_email', name: 'Send Welcome Email', description: 'Sends a welcome email to the new user')]
class SendWelcomeEmailAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        return ActionResult::success();
    }
}

// Steps with retry and timeout configured via builder
$workflow = WorkflowBuilder::create('reliable-flow')
    ->addStep('fetch_data', FetchDataAction::class, timeout: 30, retryAttempts: 3)
    ->addStep('process', ProcessAction::class, timeout: 60)
    ->build();

// Conditional steps evaluated at runtime
$workflow = WorkflowBuilder::create('conditional-flow')
    ->addStep('validate', ValidateAction::class)
    ->when('order.total > 1000', function ($builder) {
        $builder->addStep('fraud_check', FraudCheckAction::class);
    })
    ->addStep('complete', CompleteAction::class)
    ->build();

// Comparisons: === !== == != > < >= <=
'order.total > 1000'
'user.plan === "premium"'
'status != cancelled'        // bare words are treated as strings

// Truthy checks and negation
'user.active'
'!user.suspended'

// Boolean operators, with && binding tighter than ||
'order.total > 1000 && user.vip === true'
'user.vip || order.total > 5000'

// Parentheses to override precedence
'(user.vip || order.total > 5000) && !order.refunded'

// Start, resume, cancel
$instanceId = $engine->start('my-workflow', $definition->toArray(), ['key' => 'value']);
$instance = $engine->getInstance($instanceId);
$engine->resume($instanceId);
$engine->cancel($instanceId, 'No longer needed');

// Instance IDs are caller-supplied and must be unique: start() throws
// InvalidWorkflowStateException rather than overwriting an existing instance.

// Recovering a failed workflow: fix the cause, then resume to retry the
// step that failed. resume() is rejected for COMPLETED and CANCELLED.
try {
    $engine->resume($instanceId);
} catch (StepExecutionException $e) {
    // Still failing - the exception carries the real cause, not a
    // state-machine error from the failure handler.
}

// Track progress
$progress = $instance->getProgress(); // 0.0 to 100.0
$summary = $instance->getStatusSummary();

// Query instances with filters
$instances = $engine->getInstances([
    'state' => 'running',
    'definition_name' => 'order-processing',
    'created_after' => new \DateTime('-7 days'),
    'limit' => 50,
    'offset' => 0,
]);

use SolutionForest\WorkflowEngine\Support\SimpleWorkflow;

$simple = new SimpleWorkflow($storageAdapter);

// Run actions sequentially
$instanceId = $simple->sequential('user-onboarding', [
    SendWelcomeEmailAction::class,
    CreateUserProfileAction::class,
    AssignDefaultRoleAction::class,
], ['user_id' => 123]);

// Run a single action as a workflow
$instanceId = $simple->runAction(SendEmailAction::class, [
    'to' => '[email protected]',
    'subject' => 'Welcome!',
]);

// Execute from a builder
$builder = WorkflowBuilder::create('custom-flow')
    ->addStep('validate', ValidateAction::class)
    ->addStep('process', ProcessAction::class);
$instanceId = $simple->executeBuilder($builder, $context);

// Check status
$status = $simple->getStatus($instanceId);
// Returns: id, state, current_step, progress, completed_steps, failed_steps, error_message, ...

$state = $instance->getState();

$state->isActive();        // true for PENDING, RUNNING, WAITING, PAUSED
$state->isFinished();      // true for COMPLETED, FAILED, CANCELLED
$state->isSuccessful();    // true for COMPLETED
$state->isError();         // true for FAILED
$state->label();           // "Running"
$state->description();     // "The workflow is actively executing steps..."
$state->color();           // "blue" (gray, blue, yellow, orange, green, red, purple)
$state->icon();            // "▶️"
$state->canTransitionTo(WorkflowState::COMPLETED); // bool
$state->getValidTransitions(); // [WorkflowState::WAITING, ...]

use SolutionForest\WorkflowEngine\Contracts\StorageAdapter;

class DatabaseStorageAdapter implements StorageAdapter
{
    public function save(WorkflowInstance $instance): void { /* ... */ }
    public function load(string $id): WorkflowInstance { /* ... */ }
    public function findInstances(array $criteria = []): array { /* ... */ }
    public function delete(string $id): void { /* ... */ }
    public function exists(string $id): bool { /* ... */ }
    public function updateState(string $id, array $updates): void { /* ... */ }
}

use SolutionForest\WorkflowEngine\Contracts\EventDispatcher;
use SolutionForest\WorkflowEngine\Events\WorkflowStartedEvent;
use SolutionForest\WorkflowEngine\Events\WorkflowCompletedEvent;
use SolutionForest\WorkflowEngine\Events\WorkflowFailedEvent;
use SolutionForest\WorkflowEngine\Events\WorkflowCancelledEvent;
use SolutionForest\WorkflowEngine\Events\StepCompletedEvent;
use SolutionForest\WorkflowEngine\Events\StepFailedEvent;
use SolutionForest\WorkflowEngine\Events\StepRetriedEvent;

class CustomEventDispatcher implements EventDispatcher
{
    public function dispatch(object $event): void
    {
        match ($event::class) {
            WorkflowStartedEvent::class => $this->onWorkflowStarted($event),
            WorkflowCompletedEvent::class => $this->onWorkflowCompleted($event),
            WorkflowFailedEvent::class => $this->onWorkflowFailed($event),
            StepCompletedEvent::class => $this->onStepCompleted($event),
            StepFailedEvent::class => $this->onStepFailed($event),
            StepRetriedEvent::class => $this->onStepRetried($event),
            default => null,
        };
    }
}

use SolutionForest\WorkflowEngine\Contracts\Logger;

class CustomLogger implements Logger
{
    public function info(string $message, array $context = []): void { /* ... */ }
    public function warning(string $message, array $context = []): void { /* ... */ }
    public function error(string $message, array $context = []): void { /* ... */ }
    public function debug(string $message, array $context = []): void { /* ... */ }
}