Weave Code
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

Laravel Model States Laravel Package

spatie/laravel-model-states

Add state and state machine behavior to Eloquent models. Represent each state as its own class, automatically cast and store states in the database, and define clean, safe transitions and state-specific behavior in your Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require spatie/laravel-model-states
    
  2. Add the trait to your model:

    use Spatie\ModelStates\HasStates;
    
    class Payment extends Model
    {
        use HasStates;
    }
    
  3. Define a database column (e.g., state) and cast it:

    protected $casts = [
        'state' => PaymentState::class,
    ];
    
  4. Create an abstract state class (e.g., PaymentState) and concrete states (e.g., Pending, Paid):

    abstract class PaymentState extends State { ... }
    class Pending extends PaymentState { ... }
    
  5. Configure states in the abstract class:

    public static function config(): StateConfig
    {
        return parent::config()
            ->default(Pending::class)
            ->allowTransition(Pending::class, Paid::class);
    }
    

First Use Case: Transitioning States

$payment = Payment::find(1);
$payment->state->transitionTo(Paid::class); // Valid transition
$payment->save(); // Persists state to DB

Implementation Patterns

Workflows

  1. State Transitions

    • Use transitionTo() to move between states (enforced by allowTransition rules).
    • Example: Payment::find(1)->state->transitionTo(Paid::class).
  2. State-Aware Logic

    • Define methods in state classes to encapsulate behavior:
      class Paid extends PaymentState {
          public function getDiscount(): float { return 0.1; }
      }
      
    • Access via: $payment->state->getDiscount().
  3. Event Handling

    • Listen to StateChanged events (or custom events via stateChangedEvent):
      $payment->state->transitionTo(Paid::class); // Fires StateChanged
      
  4. Validation

    • Use canTransitionTo() to check allowed transitions:
      if ($payment->state->canTransitionTo(Refunded::class)) { ... }
      

Integration Tips

  • Database Migrations: Add a state column (e.g., string) to your table.
  • API Responses: Cast states to their $name for cleaner JSON:
    $payment->state->name; // Returns 'paid' instead of '\App\States\Paid'
    
  • Testing: Mock state transitions in unit tests:
    $payment->state->transitionTo(Paid::class);
    $this->assertEquals('green', $payment->state->color());
    

Gotchas and Tips

Pitfalls

  1. State Resolution

    • Ensure all state classes are in the same directory as the abstract class for auto-discovery.
    • Custom names (e.g., public static $name = 'paid') must resolve back to classes.
  2. Circular Dependencies

    • Avoid circular transitions (e.g., A → B → A) unless explicitly allowed.
  3. Database Serialization

    • States are stored as class names (or $name) in the DB. Ensure your casts match the column type.
  4. Event Timing

    • StateChanged events fire after the transition but before saving. Use saved() if you need post-save logic.

Debugging

  • Invalid Transitions: Check allowTransition rules in StateConfig.
  • Missing States: Verify all concrete states are registered (manually or via registerStatesFromDirectory).
  • Serialization Errors: Confirm $name properties (if used) are unique and valid.

Extension Points

  1. Custom Transitions

    • Override transitionTo() in a state class to add pre/post-transition logic:
      public function transitionTo(Paid::class): void {
          $this->model->applyDiscount();
          parent::transitionTo(Paid::class);
      }
      
  2. Dynamic State Rules

    • Use canTransitionTo() to implement dynamic rules (e.g., time-based):
      public function canTransitionTo(Refunded::class): bool {
          return $this->model->created_at->lte(now()->subDays(30));
      }
      
  3. State Metadata

    • Store additional data in states via properties:
      class Paid extends PaymentState {
          public function __construct(public string $transactionId) {}
      }
      
    • Access via: $payment->state->transactionId.

Configuration Quirks

  • Default Transition: Customize via config(['default_transition' => CustomTransition::class]).
  • Attribute vs. Method Config: Attributes (PHP 8+) are parsed once; method-based config() allows runtime changes.
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.
codraw/entity-migrator
codraw/doctrine-extra
codraw/aws-tool-kit
codraw/validator
codraw/workflow
codraw/open-api
codraw/cron-job
codraw/process
codraw/log
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony