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

Verbs Laravel Package

hirethunk/verbs

Verbs is a PHP event sourcing package for Laravel artisans. It delivers the benefits of event sourcing while minimizing boilerplate and jargon, making it simpler to model behavior and build systems by thinking in actions (verbs) instead of nouns.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require hirethunk/verbs
    php artisan vendor:publish --tag=verbs-migrations
    php artisan migrate
    
  2. Generate Your First Event:

    php artisan verbs:event UserRegistered
    

    This creates a basic event class in app/Events/UserRegistered.php.

  3. Fire an Event:

    UserRegistered::fire(user_id: 1, email: 'user@example.com');
    
  4. Define State (Optional):

    php artisan verbs:state UserState
    

    Customize app/States/UserState.php to track user-specific data.

First Use Case

Scenario: Track user sign-ups and validate no duplicate registrations.

// app/Events/UserRegistered.php
use HireThunk\Verbs\Attributes\StateId;

class UserRegistered extends Event
{
    public function __construct(
        #[StateId(UserState::class)]
        public int $user_id,
        public string $email,
    ) {}

    public function validate(UserState $state): bool
    {
        return $state->email === null; // Prevent duplicate emails
    }

    public function handle()
    {
        // Create user in DB or trigger side effects
        User::create(['email' => $this->email]);
    }
}

Implementation Patterns

Core Workflow

  1. Event-Driven Logic:

    • Fire events for all domain actions (e.g., OrderPlaced, PaymentProcessed).
    • Use handle() for side effects (e.g., sending emails, updating models).
  2. State Management:

    • Use #[StateId] or #[AppliesToState] to link events to states.
    • Example: Track last_login_at in UserState via UserLoggedIn event.
    #[AppliesToState(UserState::class)]
    class UserLoggedIn extends Event {
        public function apply(UserState $state) {
            $state->last_login_at = now();
        }
    }
    
  3. Validation:

    • Add validate() to enforce business rules (e.g., prevent over-subscription).
    public function validate(UserState $state): bool {
        return $state->subscriptions_count < 3;
    }
    
  4. Nested States:

    • Use #[AppliesToChildState] for hierarchical data (e.g., OrderOrderItem).
    #[AppliesToChildState(
        state_type: OrderItemState::class,
        parent_type: OrderState::class,
        id: 'item_id'
    )]
    class ItemAddedToOrder extends Event {
        public function apply(OrderItemState $state) {
            $state->quantity++;
        }
    }
    
  5. Metadata:

    • Attach contextual data (e.g., team_id, ip_address) via Verbs::createMetadataUsing().
    Verbs::createMetadataUsing(fn (Metadata $metadata) => [
        'team_id' => auth()->user()->team_id,
    ]);
    
  6. Replay Safety:

    • Mark idempotent handlers with #[Once] or use Verbs::unlessReplaying().
    #[Once(UserState::class)]
    public function handle() {
        // One-time logic (e.g., send welcome email)
    }
    

Integration Tips

  • Laravel Models: Use handle() to sync state with Eloquent models.
  • Livewire/Inertia: Fire events in UI actions (e.g., form submissions).
  • Testing: Mock Verbs::fire() and assert state changes.
    $this->expectsEvents(UserRegistered::class);
    UserRegistered::fire(user_id: 1, email: 'test@example.com');
    $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
    

Gotchas and Tips

Pitfalls

  1. State Mismatches:

    • Forgetting #[StateId] or #[AppliesToState] causes events to fire without state updates.
    • Fix: Use php artisan verbs:event and php artisan verbs:state to scaffold correctly.
  2. Circular Dependencies:

    • Events updating states that validate other events can create deadlocks.
    • Fix: Design validation to be order-agnostic (e.g., check null vs. exists).
  3. Metadata Overuse:

    • Attaching metadata to every event bloats storage.
    • Fix: Use sparingly (e.g., only for audit trails).
  4. Replay Issues:

    • Side effects in handle() (e.g., sending emails) may duplicate during replays.
    • Fix: Use #[Once] or Verbs::unlessReplaying().
  5. Nested State Complexity:

    • Deeply nested AppliesToChildState chains are hard to debug.
    • Fix: Keep hierarchies shallow (max 2–3 levels).

Debugging

  • Event History:
    $history = Verbs::historyFor(UserState::class, $user_id);
    // Inspect $history->events to see fired events.
    
  • State Dumps:
    dd(Verbs::stateFor(UserState::class, $user_id));
    
  • Validation Errors:
    • Check Verbs::lastError() for failed validations.

Extension Points

  1. Custom ID Generation: Override Verbs::generateId() for non-snowflake IDs (e.g., UUIDs).

    Verbs::generateIdUsing(fn () => Str::uuid());
    
  2. Event Storage: Extend Verbs\Storage\EventStorage to use custom databases (e.g., Redis).

    Verbs::useStorage(new RedisEventStorage());
    
  3. Metadata Providers: Register dynamic metadata via service providers.

    Verbs::createMetadataUsing(fn () => ['locale' => app()->getLocale()]);
    
  4. State Serialization: Customize Verbs\State serialization for complex types (e.g., JSON fields).

    class UserState extends State {
        protected $casts = ['preferences' => 'json'];
    }
    

Pro Tips

  • Event Naming: Use past-tense verbs (e.g., OrderShipped) for clarity.
  • State Prefixes: Stick to *State suffixes (e.g., UserState) for auto-discovery.
  • Batch Processing: Use Verbs::fireBatch() for bulk operations.
  • Livewire Integration:
    public function mount() {
        $this->userState = Verbs::stateFor(UserState::class, auth()->id());
    }
    
  • Performance: Cache frequently accessed states with Verbs::rememberState().
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4
php-http/client-implementation
phpcr/phpcr-implementation
cucumber/gherkin-monorepo
haydenpierce/class-finder
psr/simple-cache-implementation
uri-template/tests