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 Laravel-friendly event sourcing package for PHP artisans that keeps the benefits of event sourcing while cutting boilerplate and jargon. Model behavior as verbs, record events, and build projections with a clean, approachable API.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require hirethunk/verbs
    

    Publish the config and migrations:

    php artisan verbs:install
    php artisan migrate
    
  2. Define a State:

    php artisan verbs:make State User
    

    This generates a UserState class in app/States/UserState.php.

  3. Define a Verb (Event):

    php artisan verbs:make Verb User Registered
    

    This creates app/Verbs/User/Registered.php with a handle() method.

  4. First Use Case:

    // In a controller or service
    $user = UserState::create(); // Initializes state
    $user->fire(new User\Registered($user, $email = 'user@example.com'));
    

Key Files to Review

  • config/verbs.php: Configuration for event stores, IDs, and serialization.
  • app/States/: All state classes.
  • app/Verbs/: All event classes.
  • app/Listeners/: Optional event listeners.

Implementation Patterns

Core Workflow: Event Sourcing

  1. State Creation:

    $user = UserState::create(['name' => 'John']);
    
    • Initializes a new state with optional initial data.
  2. Firing Events:

    $user->fire(new User\Registered($user, $email));
    
    • Triggers the Registered event, updating the state.
    • Automatically persists the event to the event store.
  3. Replaying Events:

    $user = UserState::find($userId);
    $user->replay(); // Rebuilds state from stored events
    

Integration with Laravel

  1. Service Layer:

    class UserService {
        public function registerUser(string $email) {
            $user = UserState::create();
            $user->fire(new User\Registered($user, $email));
            return $user->id;
        }
    }
    
  2. Livewire Integration:

    public function mount() {
        $this->user = UserState::find($this->userId);
        $this->user->replay();
    }
    
    public function fireEvent() {
        $this->user->fire(new User\Updated($this->user, $this->name));
    }
    
  3. Listeners:

    class SendWelcomeEmail {
        public function handle(User\Registered $event) {
            Mail::to($event->email)->send(new WelcomeEmail());
        }
    }
    

    Register in EventServiceProvider:

    protected $listen = [
        User\Registered::class => [SendWelcomeEmail::class],
    ];
    

Advanced Patterns

  1. State Factories:

    UserState::factory()->for(User::class)->create();
    
    • Useful for testing or bulk creation.
  2. Pending Events:

    $pending = $user->pending(new User\Updated($user, 'New Name'));
    if ($pending->isValid()) {
        $pending->commit(); // Persists without immediate state update
    }
    
  3. Snapshots:

    $user->snapshot(); // Stores current state to optimize replay
    
  4. Metadata:

    $user->fire(new User\Registered($user, $email), [
        'ip_address' => request()->ip(),
    ]);
    

Gotchas and Tips

Common Pitfalls

  1. Event Store Configuration:

    • Ensure config/verbs.php has the correct event_store (e.g., database, doctrine, or custom).
    • For MySQL, use json type for the data column to avoid serialization issues:
      $table->json('data')->nullable();
      
  2. ID Generation:

    • Defaults to uuid. Change in config/verbs.php:
      'id_type' => 'snowflake', // or 'ulid'
      
    • Requires voku/ulid-php or spatie/snowflake-id for non-UUID IDs.
  3. State Serialization:

    • Avoid circular references in state properties (e.g., $user->posts where Post has a $user back-reference).
    • Use #[Verbs\Attributes\Ignore] to exclude properties:
      #[Ignore] public $temporaryData;
      
  4. Replay Side Effects:

    • Events fired during replay (e.g., fire() in handle()) are ignored by default. Use fireIfValid() or fireIfAllowed() for conditional firing:
      $event->fireIfValid(); // Only fires if event is valid
      
  5. Concurrency:

    • Verbs uses optimistic locking via last_event_id. Ensure your event store supports this (e.g., last_event_id column in the event table).

Debugging Tips

  1. Event Lifecycle:

    • Events go through phases: prepare, validate, apply, commit. Debug with:
      $event->onPrepare(fn () => Log::debug('Preparing event'));
      
  2. State Reconstruction:

    • If states aren’t rebuilding correctly, check:
      • Event handlers are idempotent (same event applied twice should yield the same state).
      • No missing or malformed events in the store.
  3. Pending Events:

    • Use ->isValid() to check if a pending event can be committed:
      if (!$pending->isValid()) {
          Log::error('Invalid event:', $pending->errors());
      }
      

Extension Points

  1. Custom Event Stores:

    • Implement Hirethunk\Verbs\Contracts\EventStore for non-database stores (e.g., Redis, Kafka).
  2. Custom Serializers:

    • Override Hirethunk\Verbs\Serializers\Serializer for custom data formats (e.g., MessagePack).
  3. State Aliases:

    • Use #[Verbs\Attributes\Alias] to map state classes to simpler names:
      #[Alias('user')] class UserState {}
      
      Then access via UserState::for('user').
  4. Testing:

    • Use Verbs::fake() to mock events:
      Verbs::fake();
      $user->fire(new User\Registered($user, $email)); // Won't persist
      
    • Reset with Verbs::assertFired(User\Registered::class) or Verbs::assertNotFired().
  5. Livewire Hooks:

    • Commit pending events before rendering:
      use Hirethunk\Verbs\Livewire\CommitPendingEvents;
      
      public function mount() {
          CommitPendingEvents::commit();
      }
      

Performance Tips

  1. Snapshots:

    • Enable snapshots for large state histories:
      $user->snapshot(); // Store current state every N events
      
    • Configure in config/verbs.php:
      'snapshot_every' => 10, // Store snapshot every 10 events
      
  2. Caching:

    • States are cached by default. Clear with:
      Verbs::stateManager()->clearCache();
      
  3. Batch Processing:

    • Use Verbs::replay($stateId) for bulk state reconstruction (e.g., during deployments).
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.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor