Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

State Workflow Laravel Package

ringierimu/state-workflow

State workflow for Laravel Eloquent models: define states and allowed transitions, guard changes with custom rules, and react to transitions via events. Centralizes business status logic so it stays clean, testable, and consistent across your app instead of scattered controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

Version 5.0.0 is a platform bump, not an API redesign. Before anything else, confirm your app satisfies the new floor:

  • PHP 8.3 or 8.4
  • Laravel 11 or 12

If you're on Laravel 10 or PHP 8.1/8.2, you stay on the 4.x line — pin "ringierimu/state-workflow": "^4.0" deliberately so Composer doesn't surprise you later.

Minimal path to first transition:

  1. composer require ringierimu/state-workflow (add ^5.0 explicitly rather than letting the resolver pick).
  2. Publish the config (php artisan vendor:publish and select the state-workflow provider/tag — confirm the exact tag in the package's README/service provider, it differs between releases).
  3. Register your model → workflow mapping in config/state-workflow.php.
  4. Add a state string column to the model's table (with a sensible default such as draft), and add the package's HasWorkflow trait to the Eloquent model.
  5. Create a workflow class extending the package's base Workflow, declaring the allowed $states and the $transitions map ('publish' => ['draft' => 'published']).

First use case to build: a publishable Post. draft → published guarded by an authorization check, driven from a controller or a form request. That exercises the three things you'll use every day — the transition map, the guard, and the persisted state column — without dragging in events or audit tables yet.

Look first at the published config file and the base Workflow class; the config is where multi-model setups go wrong, and the base class is the authoritative list of available methods for your installed version.

Implementation Patterns

Treat the transition map as the single source of truth. Everywhere you need "what can this record do next" — an admin dropdown, an API _links block, a scheduled job — derive it from the workflow definition rather than re-listing statuses in Blade or in a controller. This is the pattern that pays off most: one map, many consumers.

Authorize, then apply, then react. Keep the workflow class thin: transition table plus guards. Put side effects in model events or dedicated listeners triggered after a successful transition, so a failed email doesn't leave a half-applied state.

Wrap transitions in a transaction with a row lock. The package will happily apply a transition based on a state it read a moment ago. For anything user-facing or queued concurrently:

DB::transaction(function () use ($post) {
    $post = Post::lockForUpdate()->find($post->id);
    $post->workflow()->apply('publish'); // confirm exact accessor in your version
});

Audit with an append-only table. A state_transitions table (model_type, model_id, from, to, transition, actor_id, created_at) written from a model event gives you both a debugging trail and the reporting that product will ask for in month two.

Multiple workflows per model is fine — use separate state columns. A Post with state and moderation_state is clearer than one workflow with twelve states and a tangle of guards.

Validate the requested transition at the edge. A form request rule that checks the transition is currently legal turns a class of 500s into a 422 and keeps the controller boring.

Testing: assert against the database column and the audit table, not against internal package objects. Cover (a) the happy path, (b) an illegal transition throwing, (c) the guard rejecting. A cheap but high-value test: assert every state declared in $states is reachable from the initial state by walking the transition graph — this catches typos that otherwise only surface in production.

Gotchas and Tips

5.0.0 contains no documented behavioural changes — the only entry is the Laravel 11/12 and PHP 8.3/8.4 floor. That's still a breaking upgrade in Composer's eyes, so treat it as a coordinated change: update composer.json require.php, your CI matrix, your Docker/Forge image, and any platform config together. A stale "platform": {"php": "8.2"} block in composer.json will silently hold you at 4.x.

Diagnosing a blocked upgrade: composer why-not ringierimu/state-workflow 5.0.0 names the conflicting package immediately. Most often it's a transitive dependency that hasn't published Laravel 12 support yet — not this package.

Config caching bites. After editing the workflow map, php artisan config:clear. Under Octane, the config is frozen at worker boot; restart workers, don't just clear.

Never write the state column directly ($post->state = 'published'). It bypasses guards, events, and your audit trail, and produces records whose history is a lie. If you must do a data migration, write the audit rows in the same migration.

Typos in the transition map fail quietly at definition time and loudly at runtime — usually as an "invalid transition" exception in a job at 3am. The reachability test above is the cheapest insurance.

Guard failures and illegal-transition failures often surface as the same exception type. If your UI needs to distinguish "you're not allowed" from "that's not a valid move from here", catch and re-map them explicitly rather than relying on message strings.

Extension points worth knowing: subclass the base Workflow for shared behaviour across your domain's workflows; the published config for swapping implementations or scoping by model; model events for side effects. Avoid forking the package to add a feature — most additions belong in a subclass or a listener.

Log the from/to/transition name on every apply (debug level is fine). When someone asks "why is this record in this state", the answer is usually one grep away if you logged it, and unrecoverable if you didn't.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin