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

States Life Cycle Laravel Package

teknoo/states-life-cycle

View on GitHub
Deep Wiki
Context7

Getting Started

First, confirm the package is installable in your Laravel version. It is archived and has no Laravel provider, so treat it as a low-level domain component, not a framework feature.

composer require teknoo/states-life-cycle

Then inspect, in this order:

  1. vendor/teknoo/states-life-cycle/README.md
  2. vendor/teknoo/states-life-cycle/src/
  3. vendor/teknoo/states-life-cycle/tests/
  4. vendor/teknoo/states/ for the base Stateable / State contracts and traits

The tests are especially important because the package is archived and examples may lag behind the actual API.

A good first use case is a single Eloquent model with a small lifecycle, such as an Order:

draft -> pending -> paid -> fulfilled
   \-> cancelled

Persist only the state name in the database, for example orders.state, and keep the package state objects as transient domain objects. Start by wrapping the package service in one application-level adapter:

namespace App\Services;

use App\Models\Order;

final class OrderLifecycle
{
    public function __construct(private readonly object $lifeCycle)
    {
    }

    public function transition(Order $order, string $to): Order
    {
        // Adapt to the installed package API: hydrate/attach state,
        // invoke the lifecycle transition, then persist the scalar state.
        $this->lifeCycle->transition($order, $to);

        $order->state = $to;
        $order->save();

        return $order;
    }
}

Register the adapter in a service provider and keep controllers/actions dependent on OrderLifecycle, not on package classes directly.

Implementation Patterns

Use the package as the transition engine, but keep Laravel orchestration around it.

State classes per domain state

Create a small class for each state and keep behavior close to the state:

namespace App\States\Order;

final class Paid
{
    public function canCancel(): bool
    {
        return true;
    }
}

If the package expects state registration or mapping, centralize it in a config file or dedicated factory rather than scattering class names through controllers.

Persist scalars, rehydrate objects

Store state as a string or enum-backed column. Do not serialize package state objects into the database.

$order = Order::whereKey($id)->lockForUpdate()->firstOrFail();

$orderLifecycle->transition($order, 'paid');

Use a model observer or saving guard to prevent accidental direct writes to state from forms, seeders, or ad-hoc code. The lifecycle adapter should be the normal write path.

Wrap transitions in transactions

DB::transaction(function () use ($order, $orderLifecycle) {
    $orderLifecycle->transition($order, 'paid');

    DB::afterCommit(fn () => event(new OrderPaid($order->id)));
});

Keep side effects such as mail, webhooks, and queue dispatches after commit. If the package’s automatic behavior performs side effects synchronously, make those behaviors thin and queue the slow work.

Use actions instead of controllers

final class PayOrder
{
    public function __construct(private readonly OrderLifecycle $lifeCycle)
    {
    }

    public function __invoke(Order $order): Order
    {
        return DB::transaction(fn () => $this->lifeCycle->transition($order, 'paid'));
    }
}

Controllers, form requests, jobs, and Artisan commands should all call the same action. That keeps HTTP, queue, and CLI behavior consistent.

Expose state to authorization and UI

Use policies/gates for “can this user cancel this order?” and expose allowed transitions as a small read model:

final class OrderStatePresenter
{
    public function allowedTransitions(Order $order): array
    {
        return match ($order->state) {
            'pending' => ['paid', 'cancelled'],
            'paid' => ['fulfilled', 'cancelled'],
            default => [],
        };
    }
}

Integrate with Laravel events carefully

Use Laravel events for application integration, but do not duplicate the package’s lifecycle events. Pick one source of truth for “state changed” and document it. If the package emits events, map them into Laravel events in one adapter.

Test the graph, not the package internals

Unit-test state transitions, invalid transitions, and adapter behavior. Feature-test the HTTP action. Use RefreshDatabase and assert both the persisted state and that side effects were queued or fired after commit.

Gotchas and Tips

  • Archived package: It has no active maintainer, no Laravel integration, and zero dependents. Pin the version, isolate it behind an adapter, and be prepared to replace it with a custom state machine or Symfony Workflow.

  • Composer compatibility: Check PHP and dependency constraints before adoption:

    composer why-not teknoo/states-life-cycle
    composer show teknoo/states-life-cycle
    
  • No auto-discovery: There is no Laravel service provider to discover. Bind the package service or your adapter manually in AppServiceProvider.

  • Do not bind lifecycle managers as singletons blindly: Lifecycle services may hold or cache state. Prefer scoped() rather than singleton() if the service is stateful, especially with Laravel Octane and queue workers.

  • Concurrency: Two requests can try to transition the same order. Use lockForUpdate() or an optimistic version column, then re-check the current state inside the transaction.

  • Invalid transitions: Catch package-specific exceptions and map them to HTTP 409 Conflict or a validation error. Do not let them become generic 500s.

  • Partial writes: If automatic lifecycle behavior throws after some writes, the transaction protects the database but not external side effects. Keep external side effects after commit or use an outbox pattern.

  • Recursive or nested transitions: If state entry/exit behavior triggers another transition, guard against recursion and define whether nested transitions are allowed.

  • Direct state writes: Eloquent mass assignment, seeders, imports, and admin panels can bypass the lifecycle. Protect the state attribute and document the only supported write path.

  • Event duplication: Laravel model events plus package lifecycle events can fire twice for the same change. Centralize state-change notifications in the adapter.

  • Long-running automatic behavior: If the package runs behavior synchronously on transition, move mail, webhooks, PDF generation, and third-party calls into queued jobs.

  • No config quirks to rely on: Expect manual wiring. If the installed version uses reflection or annotations for discovery, run composer dump-autoload after moving state classes and clear Laravel caches during deployment.

  • Migration risk: Because the package is archived, define your own domain-level interface early. That interface is the seam that lets you replace the package later without rewriting controllers or jobs.

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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk