Installation:
composer require xentixar/workflow-manager
php artisan vendor:publish --tag=workflow-manager-config
php artisan vendor:publish --tag=workflow-manager-migrations
php artisan migrate
Configure config/workflow-manager.php with your roles (e.g., 'admin' => 'Admin').
Define an Enum:
Create a PHP enum for your workflow states (e.g., Status::class):
enum Status: string {
case DRAFT = 'draft';
case REVIEW = 'review';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
}
Add to a Filament Resource:
Use StateSelect in your Filament resource:
use Xentixar\WorkflowManager\Components\StateSelect;
StateSelect::make('status')
->setRole('admin') // Matches config key
->enum(Status::class)
->required();
First Transition:
In your resource's edit() or create() method, attach the workflow:
public static function form(Form $form): Form {
return $form
->schema([
// ... other fields
StateSelect::make('status')
->setRole('admin')
->enum(Status::class)
->required(),
]);
}
Create a workflow for a Post model with transitions:
php artisan make:workflow PostStatus --role=admin --enum=Status
This generates a migration and workflow definition. Edit the migration to define transitions:
Schema::create('post_status_workflows', function (Blueprint $table) {
$table->id();
$table->string('role')->index();
$table->string('enum')->index();
$table->json('transitions')->comment('Allowed transitions: {from: "draft", to: "review"}');
$table->timestamps();
});
Populate the transitions field with allowed state changes (e.g., ['from' => 'draft', 'to' => 'review']).
State Field in Forms/Tables:
Reuse StateSelect in forms and tables for consistency:
// Form
StateSelect::make('status')
->setRole('admin')
->enum(Status::class)
->required()
->live(onChange: fn ($value) => $this->set('last_updated_at', now()));
// Table
Tables\Columns\SelectColumn::make('status')
->options(Status::class)
->color(fn ($state) => match ($state) {
Status::DRAFT => 'gray',
Status::PUBLISHED => 'green',
default => 'blue',
});
Transition Buttons: Dynamically render transition buttons based on current state:
use Xentixar\WorkflowManager\Facades\WorkflowManager;
$currentState = $record->status;
$allowedTransitions = WorkflowManager::getAllowedTransitions(
role: 'admin',
enum: Status::class,
from: $currentState
);
foreach ($allowedTransitions as $transition) {
Button::make('Transition to ' . $transition['to'])
->action(fn () => $this->updateStatus($transition['to']))
->visible(fn () => $currentState === $transition['from']);
}
Workflow-Aware Logic: Use middleware or policies to enforce workflow rules:
// Policy
public function update(User $user, Post $post): bool {
return WorkflowManager::canTransition(
role: 'admin',
enum: Status::class,
from: $post->status,
to: $newStatus
);
}
Conditional Transitions:
Attach conditions to transitions via the conditions field in the workflow table:
// Migration
$table->json('conditions')->nullable()->comment('e.g., {to: "review", callback: "App\\Policies\\PostPolicy@canReview"}');
// Usage
WorkflowManager::canTransition(
role: 'admin',
enum: Status::class,
from: Status::DRAFT,
to: Status::REVIEW,
conditions: ['callback' => 'App\Policies\PostPolicy@canReview']
);
Bulk Transitions: Add a bulk action to Filament tables:
Tables\Actions\Action::make('bulkTransition')
->action(fn (Collection $records, string $toState) => {
foreach ($records as $record) {
$record->update(['status' => $toState]);
}
})
->modalHeading('Transition Selected Items')
->modalDescription('Select the new status:')
->modalSubmitAction(fn (Collection $records) => $this->bulkTransition($records, $this->modalState))
->modalContent(fn (Collection $records) => StateSelect::make('status')
->enum(Status::class)
->required()
->live(onChange: fn ($value) => $this->modalState = $value));
Workflow Diagrams: Generate interactive diagrams for admin panels:
use Xentixar\WorkflowManager\Components\WorkflowDiagram;
WorkflowDiagram::make()
->role('admin')
->enum(Status::class)
->width('full');
Unit Tests: Test transition logic in isolation:
public function test_can_transition_from_draft_to_review() {
$this->assertTrue(
WorkflowManager::canTransition(
role: 'admin',
enum: Status::class,
from: Status::DRAFT,
to: Status::REVIEW
)
);
}
Feature Tests: Test Filament interactions:
public function test_status_transition_in_filament() {
$this->actingAs($adminUser)
->get('/admin/posts/1/edit')
->assertSee('Draft')
->press('Transition to Review')
->assertSee('Review');
}
Role Mismatch:
role in StateSelect doesn’t match the workflow’s role.setRole() matches the workflow’s role field in the database.WorkflowManager::getWorkflows('admin', Status::class) to verify loaded workflows.Enum Mismatch:
enum field in the workflow table matches your enum class name.Missing Migrations:
php artisan migrate post-installation.Circular References:
A → B and B → A without conditions).conditions to gate reverse transitions or document workflow rules clearly.Livewire Stale State:
StateSelect not updating immediately after a transition.live() with onChange to refresh dependent fields:
StateSelect::make('status')
->live(onChange: fn ($value) => $this->emit('refreshForm'));
Log Workflow Data: Add temporary logging to inspect workflows:
\Log::info('Workflows for admin:', [
'workflows' => WorkflowManager::getWorkflows('admin', Status::class),
]);
Check Database:
Verify workflow definitions in the workflow_transitions table:
SELECT * FROM workflow_transitions WHERE role = 'admin' AND enum = 'App\\Enums\\Status';
Enable Query Logging: Temporarily enable Laravel’s query log to debug migration issues:
DB::enableQueryLog();
// Run migration code...
dd(DB::getQueryLog());
Default Role:
StateSelect, the package defaults to the first role in config/workflow-manager.php.Enum Serialization:
enum field in the database stores the fully qualified class name (e.g., App\Enums\Status).get_class($enum) to ensure consistency.Transition Conditions:
conditions field. Use arrays for complex logic:
How can I help you explore Laravel packages today?