Skip to main content
WorkflowTrait provides a convenient way to manage workflows within classes, making it easier to encapsulate workflow logic and access results.

Overview

The trait provides two main methods:
  • execute() - Runs a workflow and stores the result
  • run() - Accesses the stored workflow result
This pattern is useful when you want to encapsulate workflow logic within a class and maintain access to the execution results.

Basic usage

Here’s how to use WorkflowTrait in your classes:
use Chevere\Workflow\Traits\WorkflowTrait;
use function Chevere\Workflow\{workflow, sync, variable, response};

class OrderProcessor
{
    use WorkflowTrait;

    public function process(int $orderId): void
    {
        $workflow = workflow(
            validate: sync(
                ValidateOrder::class,
                id: variable('orderId')
            ),
            charge: sync(
                ChargePayment::class,
                order: response('validate')
            ),
            fulfill: sync(
                FulfillOrder::class,
                order: response('charge')
            )
        );

        $this->execute($workflow, orderId: $orderId);
    }

    public function getStatus(): string
    {
        return $this->run()->response('fulfill', 'status')->string();
    }

    public function getOrderId(): int
    {
        return $this->run()->response('fulfill', 'orderId')->int();
    }
}
Usage:
$processor = new OrderProcessor();
$processor->process(12345);

echo $processor->getStatus();   // "completed"
echo $processor->getOrderId();  // 12345

Methods reference

execute()

Runs a workflow and stores the result for later access. Signature:
private function execute(
    WorkflowInterface $workflow,
    ContainerInterface $container = new Container(),
    mixed ...$argument
): void
Parameters:
  • $workflow - The workflow to execute
  • $container - Optional PSR-11 container for dependency injection
  • ...$argument - Named arguments matching the workflow’s variables
Example:
$this->execute(
    $workflow,
    userId: 123,
    email: '[email protected]'
);

run()

Accesses the stored workflow execution result. Signature:
public function run(): RunInterface
Returns: RunInterface with access to job responses and metadata Throws: BadMethodCallException if called before execute() Example:
$result = $this->run();
$response = $result->response('jobName')->string();

Using with Action classes

WorkflowTrait works particularly well with Chevere Action classes:
use Chevere\Action\Action;
use Chevere\Workflow\Traits\WorkflowTrait;

class ProcessUserRegistration extends Action
{
    use WorkflowTrait;

    public function __invoke(string $email, string $password): array
    {
        $workflow = workflow(
            validate: sync(
                ValidateCredentials::class,
                email: variable('email'),
                password: variable('password')
            ),
            create: sync(
                CreateUser::class,
                email: variable('email'),
                password: variable('password')
            ),
            notify: async(
                SendWelcomeEmail::class,
                userId: response('create', 'id'),
                email: variable('email')
            )
        );

        $this->execute(
            $workflow,
            email: $email,
            password: $password
        );

        return [
            'userId' => $this->run()->response('create', 'id')->int(),
            'email' => $email,
            'notified' => !$this->run()->skip()->contains('notify')
        ];
    }
}
Usage:
$action = new ProcessUserRegistration();
$result = $action(
    email: '[email protected]',
    password: 'secure123'
);

// Returns:
// [
//     'userId' => 1,
//     'email' => '[email protected]',
//     'notified' => true
// ]

With dependency injection

Pass a PSR-11 container when your workflow uses Action classes with dependencies:
use Chevere\Container\Container;

class EmailCampaignProcessor
{
    use WorkflowTrait;

    public function __construct(
        private LoggerInterface $logger,
        private MailerInterface $mailer
    ) {}

    public function send(array $recipients): void
    {
        $container = new Container(
            logger: $this->logger,
            mailer: $this->mailer
        );

        $workflow = workflow(
            prepare: sync(
                PrepareEmails::class,  // Requires LoggerInterface
                recipients: variable('recipients')
            ),
            send: async(
                SendBulkEmail::class,  // Requires MailerInterface
                emails: response('prepare')
            )
        );

        $this->execute($workflow, $container, recipients: $recipients);
    }

    public function getSentCount(): int
    {
        return $this->run()->response('send', 'count')->int();
    }
}

Accessing workflow metadata

The run() method returns a RunInterface that provides access to various workflow metadata:
class ReportGenerator
{
    use WorkflowTrait;

    public function generate(array $data): void
    {
        $workflow = workflow(
            analyze: sync(AnalyzeData::class, data: variable('data')),
            format: sync(FormatReport::class, analysis: response('analyze')),
            export: sync(ExportPDF::class, report: response('format'))
        );

        $this->execute($workflow, data: $data);
    }

    public function getReport(): string
    {
        return $this->run()->response('export')->string();
    }

    public function getExecutionId(): string
    {
        return $this->run()->uuid();
    }

    public function getWorkflow(): WorkflowInterface
    {
        return $this->run()->workflow();
    }

    public function getArguments(): array
    {
        return $this->run()->arguments()->toArray();
    }

    public function wasSkipped(string $job): bool
    {
        return $this->run()->skip()->contains($job);
    }
}

Error handling

If run() is called before execute(), it throws a BadMethodCallException:
use BadMethodCallException;

class MyProcessor
{
    use WorkflowTrait;

    public function getResult(): string
    {
        try {
            return $this->run()->response('job')->string();
        } catch (BadMethodCallException $e) {
            return 'Workflow not executed yet';
        }
    }
}

$processor = new MyProcessor();
echo $processor->getResult(); // "Workflow not executed yet"
Workflow execution exceptions are propagated normally:
use Chevere\Workflow\Exceptions\RunnerException;

class DataProcessor
{
    use WorkflowTrait;

    public function process(array $data): void
    {
        $workflow = workflow(
            validate: sync(
                ValidateData::class,
                data: variable('data')
            )
        );

        try {
            $this->execute($workflow, data: $data);
        } catch (RunnerException $e) {
            // Handle validation failure
            throw new ProcessingException(
                "Failed at job: {$e->name}",
                previous: $e->throwable
            );
        }
    }
}

Real-world example: Image pipeline

Here’s a complete example showing WorkflowTrait in a real-world scenario:
use Chevere\Action\Action;
use Chevere\Workflow\Traits\WorkflowTrait;
use function Chevere\Workflow\{workflow, async, sync, variable, response};

class ImagePipeline extends Action
{
    use WorkflowTrait;

    public function __invoke(string $imagePath, string $outputDir): array
    {
        $workflow = workflow(
            // Resize in parallel
            thumb: async(
                ResizeImage::class,
                file: variable('image'),
                width: 150,
                height: 150
            ),
            medium: async(
                ResizeImage::class,
                file: variable('image'),
                width: 800
            ),
            large: async(
                ResizeImage::class,
                file: variable('image'),
                width: 1920
            ),
            // Optimize in parallel
            optimizeThumb: async(
                OptimizeImage::class,
                file: response('thumb')
            ),
            optimizeMedium: async(
                OptimizeImage::class,
                file: response('medium')
            ),
            optimizeLarge: async(
                OptimizeImage::class,
                file: response('large')
            ),
            // Store all files
            store: sync(
                StoreFiles::class,
                files: [
                    response('optimizeThumb'),
                    response('optimizeMedium'),
                    response('optimizeLarge')
                ],
                directory: variable('outputDir')
            )
        );

        $this->execute(
            $workflow,
            image: $imagePath,
            outputDir: $outputDir
        );

        return $this->getResults();
    }

    private function getResults(): array
    {
        return [
            'files' => [
                'thumb' => $this->run()->response('optimizeThumb', 'path')->string(),
                'medium' => $this->run()->response('optimizeMedium', 'path')->string(),
                'large' => $this->run()->response('optimizeLarge', 'path')->string(),
            ],
            'sizes' => [
                'thumb' => $this->run()->response('optimizeThumb', 'size')->int(),
                'medium' => $this->run()->response('optimizeMedium', 'size')->int(),
                'large' => $this->run()->response('optimizeLarge', 'size')->int(),
            ],
            'executionId' => $this->run()->uuid(),
        ];
    }
}
Usage:
$pipeline = new ImagePipeline();
$result = $pipeline(
    imagePath: '/uploads/photo.jpg',
    outputDir: '/processed/'
);

print_r($result);
// [
//     'files' => [
//         'thumb' => '/processed/photo-thumb.jpg',
//         'medium' => '/processed/photo-medium.jpg',
//         'large' => '/processed/photo-large.jpg',
//     ],
//     'sizes' => [
//         'thumb' => 15360,
//         'medium' => 102400,
//         'large' => 512000,
//     ],
//     'executionId' => '550e8400-e29b-41d4-a716-446655440000'
// ]

Benefits

When to use WorkflowTrait:
  • You need to encapsulate workflow logic within a class
  • Multiple methods need access to the workflow results
  • You want to expose workflow results through a clean public API
  • You’re building reusable workflow components
Alternative approach: If you only need to run a workflow once without storing the result, use the run() function directly instead of WorkflowTrait.

Build docs developers (and LLMs) love