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 Laravel Eloquent models. Represent each state as a class, automatically serialize to/from the database, and perform clean, explicit transitions with configurable rules—ideal for workflows like payments, orders, and approvals.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

  • State Pattern Alignment: The package leverages the State Pattern and State Machine concepts, making it ideal for domain-driven design (DDD) where entities (e.g., Payment, Order) have well-defined lifecycle states (e.g., Pending, Shipped, Cancelled). This aligns with Laravel’s Eloquent ORM and promotes separation of concerns by encapsulating state-specific logic in dedicated classes.
  • Eloquent Integration: Built for Laravel’s Eloquent models, it integrates seamlessly with existing database schemas, migrations, and model relationships. The use of model casting ($casts) ensures type safety and serialization/deserialization transparency.
  • Extensibility: Supports multiple state fields per model (e.g., Payment could have state and fulfillment states), enabling complex workflows without bloating model logic. The attribute-based configuration (PHP 8+) further reduces boilerplate.

Integration Feasibility

  • Low Friction: Requires minimal changes to existing models—only the addition of the HasStates trait, a cast definition, and state classes. No database migrations are needed if the state column already exists (though it’s recommended to add one).
  • Backward Compatibility: Works with Laravel 8+ and PHP 7.4+. The package’s MIT license and Spatie’s reputation reduce adoption risk.
  • Testing Support: Includes built-in test suites and mocking utilities, easing CI/CD integration.

Technical Risk

  • State Resolution Overhead: Custom state names (e.g., Paid::class vs. paid) require directory-based autoloading, which could introduce subtle bugs if state classes are misplaced or renamed. Mitigation: Use IDE refactoring tools and enforce naming conventions.
  • Performance: Serialization/deserialization of state objects adds minor overhead during model hydration. Benchmark if used in high-throughput systems (e.g., 10K+ models/sec).
  • Event System: Custom StateChanged events must be listened to if side effects (e.g., notifications, audits) are needed. Default events may not cover all use cases.
  • State Transition Logic: Complex workflows (e.g., guarded transitions) require careful StateConfig setup. Misconfigured transitions could lead to inconsistent state machines.

Key Questions

  1. Domain Complexity: How many state fields per model are needed? Will multiple fields require cross-field validation (e.g., "Cannot ship if payment is Failed")?
  2. State Persistence: Should state transitions be audited (e.g., via laravel-audit or custom logs)?
  3. Performance: Will state resolution become a bottleneck? Consider caching resolved states if used in read-heavy workflows.
  4. Team Familiarity: Is the team comfortable with the State Pattern? If not, provide workshops or internal docs with examples.
  5. Custom Logic: Are there state-specific side effects (e.g., triggering webhooks, updating related models) that need to be handled outside the state classes?
  6. Testing Strategy: How will state transitions be tested? The package supports mocking, but property-based testing (e.g., with PestPHP) could validate edge cases.

Integration Approach

Stack Fit

  • Laravel Ecosystem: Optimized for Laravel’s Eloquent, Laravel Nova, and Livewire (for UI state visualization). Works alongside Laravel Scout (if state affects search relevance) and Laravel Horizon (for queued state transitions).
  • PHP Version: Requires PHP 7.4+ (PHP 8+ for attributes). Ensure your composer.json and CI pipeline support this.
  • Database: Compatible with MySQL, PostgreSQL, SQLite. No schema changes required if the state column exists (but recommended for clarity).
  • Testing: Integrates with PestPHP and PHPUnit. Use factories to seed models with specific states.

Migration Path

  1. Assessment Phase:
    • Audit existing models to identify candidate entities for state management (e.g., Order, Subscription, Payment).
    • Map current state logic (e.g., if ($order->status === 'shipped')) to state classes.
  2. Pilot Implementation:
    • Start with a single model (e.g., Payment) to validate the approach.
    • Use feature flags to toggle state logic in production.
  3. Incremental Rollout:
    • Add state support to one model at a time, prioritizing high-impact domains.
    • Update APIs/controllers to use state methods (e.g., $payment->state->transitionTo(Paid::class)).
  4. Deprecation:
    • Phase out legacy state logic (e.g., if ($order->status === 'cancelled')) in favor of state methods.
    • Use deprecation warnings in legacy code.

Compatibility

  • Laravel Versions: Tested on Laravel 8+. For Laravel 7, use v1.x of the package.
  • Package Conflicts: No known conflicts with Spatie’s other packages (e.g., laravel-permission). Monitor for namespace collisions if using custom state directories.
  • Third-Party Integrations:
    • Laravel Nova: Use resource tools to display state-specific UI (e.g., badges for Paid vs. Failed).
    • Livewire: Bind state transitions to button clicks or form submissions.
    • Queues: Offload state transitions to Laravel Queues for async processing (e.g., Payment::find($id)->state->transitionTo(Paid::class)->dispatch()).

Sequencing

  1. Setup:
    • Publish the config (php artisan vendor:publish --provider="Spatie\ModelStates\ModelStatesServiceProvider").
    • Configure default transitions and custom events in config/model-states.php.
  2. Model Integration:
    • Add HasStates trait and cast definitions to target models.
    • Create abstract state classes and concrete state implementations.
  3. State Directory Structure:
    app/
      States/
        Payment/
          PaymentState.php
          Pending.php
          Paid.php
          Failed.php
    
  4. Testing:
    • Write unit tests for state transitions (e.g., PendingPaid but not PendingCancelled).
    • Test serialization/deserialization with custom state names.
  5. Monitoring:
    • Log StateChanged events to Laravel Log or a dedicated table.
    • Set up alerts for invalid transitions (e.g., via try-catch in transition logic).

Operational Impact

Maintenance

  • Boilerplate Reduction: State logic is encapsulated in classes, reducing model bloat. Changes to state behavior (e.g., adding a Refunded state) require minimal model updates.
  • Configuration Over Code: State transitions are defined in StateConfig, making them easier to modify than scattered if-else logic.
  • Documentation: Maintain a runbook for:
    • Adding new states.
    • Debugging transition failures.
    • Customizing state serialization.

Support

  • Debugging: Use dd($model->state) to inspect current state. Log transition attempts for auditing.
  • Common Issues:
    • State Not Found: Ensure state classes are in the correct directory and extend the abstract class.
    • Transition Denied: Verify allowTransition() rules in StateConfig.
    • Serialization Errors: Check for invalid state names (e.g., hyphens) or missing $name properties.
  • Support Channels: Leverage Spatie’s GitHub Discussions and Laravel Forums for troubleshooting.

Scaling

  • Database Load: State transitions are single-row updates, so scaling follows Laravel’s standard practices (e.g., database connection pooling, read replicas).
  • Concurrency: Use database transactions for atomic state changes:
    DB::transaction(function () use ($payment) {
        $payment->state->transitionTo(Paid::class);
        $payment->update(['updated_at' => now()]);
    });
    
  • Caching: Cache frequently accessed states (e.g., Pending vs. Paid counts) using Laravel’s cache:
    Cache::remember('payment_states', now()->addHours(1), function () {
        return Payment::getStates();
    });
    

Failure Modes

Failure Scenario Impact Mitigation
Invalid state transition Data inconsistency Use try-catch and rollback transactions. Log failed attempts.
State class missing Serialization errors Enforce **
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai