phpmentors/workflower
Workflower is an open-source BPMN 2.0 workflow engine for PHP. Import BPMN process definitions and run process instances with tasks, events, gateways, lanes, and sequence flows, plus interfaces for persistence via serialization/deserialization.
Install the Package
composer require phpmentors/workflower "1.4.*"
Define a BPMN Workflow
.bpmn file (e.g., loan_request.bpmn) in resources/config/workflower/.[processData.foo == 'bar']).Create a Process Entity
Implement ProcessContextInterface and WorkflowSerializableInterface in a Laravel Eloquent model:
use PHPMentors\Workflower\Process\ProcessContextInterface;
use PHPMentors\Workflower\Persistence\WorkflowSerializableInterface;
use PHPMentors\Workflower\Workflow\Workflow;
class LoanRequest implements ProcessContextInterface, WorkflowSerializableInterface
{
protected $workflow;
protected $serializedWorkflow;
public function getProcessData(): array
{
return [
'amount' => $this->amount,
'status' => $this->status,
];
}
// Implement WorkflowSerializableInterface methods...
}
Register Workflower in Laravel Add a service provider to bootstrap Workflower:
// app/Providers/WorkflowerServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use PHPMentors\Workflower\Persistence\WorkflowSerializerInterface;
use PHPMentors\Workflower\Persistence\WorkflowSerializer;
class WorkflowerServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(WorkflowSerializerInterface::class, function ($app) {
return new WorkflowSerializer();
});
// Register process definitions
$this->app->bind('workflower.definition_repository', function ($app) {
$repository = new \PHPMentors\Workflower\Definition\ProcessDefinitionRepository();
$repository->addFromDirectory(__DIR__.'/../../resources/config/workflower');
return $repository;
});
}
}
Register the provider in config/app.php.
Start a Process
Use the Process facade or service to trigger workflows:
use PHPMentors\Workflower\Process\Process;
$process = app(Process::class);
$loanRequest = new LoanRequest(['amount' => 1000]);
$processInstance = $process->start('LoanRequestProcess', $loanRequest);
BPMN Design → Laravel Model
ProcessAwareInterface.ProcessContextInterface::getProcessData().Process-Aware Services
Tag Laravel services with phpmentors_workflower.process_aware to link them to workflows:
# config/services.php
'workflower.services' => [
'App\Services\LoanApprovalService' => [
'workflow' => 'LoanRequestProcess',
'context' => 'app',
],
],
Implement ProcessAwareInterface in services:
class LoanApprovalService implements ProcessAwareInterface
{
protected $process;
public function setProcess(Process $process)
{
$this->process = $process;
}
public function approve(LoanRequest $loan)
{
$this->process->completeWorkItem($loan);
// Additional business logic...
}
}
Workflow Execution Flow
event(new LoanRequestSubmitted($loanRequest));
completeWorkItem() on the process instance.
$process->completeWorkItem($loanRequest);
ProcessContextInterface::getProcessData() for dynamic routing.
// In BPMN: [processData.approved == true]
Persistence
$loanRequest->setSerializedWorkflow($workflow->serialize());
$loanRequest->save();
$workflow = $loanRequest->getWorkflow();
Querying Workflows Use Laravel query scopes to filter processes by activity or status:
class LoanRequest extends Model
{
public function scopePendingApproval($query)
{
return $query->where('current_activity', 'approve_task');
}
}
| Use Case | Implementation Pattern |
|---|---|
| Approval Workflows | Use UserTask in BPMN + ProcessAware services for manual approvals. |
| Conditional Branching | Define ExclusiveGateway in BPMN with expressions like [processData.amount > 1000]. |
| Parallel Tasks | Use ParallelGateway in BPMN to split/join workflow paths. |
| External API Calls | Use ServiceTask in BPMN + Laravel HTTP clients to call APIs. |
| Notifications | Trigger Laravel notifications when tasks are assigned (work_item_assigned event). |
| Retry Mechanisms | Implement ProcessListenerInterface to handle failed tasks. |
Undefined Sequence Flow Evaluation
ExclusiveGateway may not behave as expected if expressions overlap or are ambiguous.Serialization Failures
ProcessContextInterface::getProcessData() may not serialize properly.__serialize()/__unserialize() in custom objects.Laravel Service Container Conflicts
$this->app->bind('phpmentors_workflower.process', function ($app) {
return new Process($app['workflower.definition_repository']);
});
Performance with Large Workflows
$repository = new ProcessDefinitionRepository();
$repository->setCache(new \Symfony\Component\Cache\Simple\FilesystemCache());
Missing Workflow Context
workflow_contexts in WorkflowerBundle (if used).definition_dir points to a valid directory with .bpmn files.Log Workflow State Add a listener to log transitions:
$process->addListener(new class implements ProcessListenerInterface {
public function onTransition(Process $process, Transition $transition)
{
\Log::info("Transition: {$transition->getSource()->getId()} -> {$transition->getTarget()->getId()}");
}
});
Inspect Process Data
Dump ProcessContextInterface::getProcessData() to verify values:
\Log::debug('Process Data:', $loanRequest->getProcessData());
Validate BPMN Files Use online validators or tools like BPMN.io to check for errors before importing.
Handle Serialization Errors Catch exceptions during deserialization:
try {
$workflow = $loanRequest->getWorkflow();
} catch (\RuntimeException $e) {
\Log::error("Workflow deserialization failed: " . $e->getMessage());
$workflow = $loanRequest->getWorkflow()->reset();
}
Custom Serializers
Extend WorkflowSerializerInterface for custom storage (e.g., Redis):
class RedisWorkflowSerializer implements WorkflowSerializerInterface
{
public function serialize(Workflow $workflow): string
{
return json_encode($workflow->toArray());
}
public function deserialize(string $serialized): Workflow
{
return Workflow::fromArray(json_decode($serialized, true));
}
}
Custom Process Listeners
Implement ProcessListenerInterface to react to workflow events:
class NotificationListener implements ProcessListenerInterface
How can I help you explore Laravel packages today?