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 Column Watcher Laravel Package

ascend/laravel-column-watcher

Attribute-based “column watchers” for Laravel Eloquent models. Trigger handlers only when specific attributes change (before/after save), with support for multiple watchers, queueable handlers, events, and Octane compatibility—avoiding bulky, monolithic model observers.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require ascend/laravel-column-watcher
    

    Publish the config (optional):

    php artisan vendor:publish --provider="Ascend\ColumnWatcher\ColumnWatcherServiceProvider"
    
  2. First Use Case: Watch a single column on a model:

    use Ascend\ColumnWatcher\Watch;
    
    class User extends Model
    {
        #[Watch('email', handler: EmailUpdatedHandler::class)]
        public string $email;
    }
    
  3. Run Migrations/Tests: Verify the package works by triggering a column update and checking if the handler executes.

Where to Look First

  • Usage Section in the README for basic syntax.
  • ColumnChange object docs to understand event payloads.
  • config/column-watcher.php for global settings (e.g., default queue connection).

Implementation Patterns

Core Workflows

  1. Column-Specific Logic: Replace observer updated() methods with granular handlers:

    // Instead of:
    protected static function updated(Model $model): void
    {
        if ($model->isDirty('email')) { ... }
    
        if ($model->isDirty('status')) { ... }
    }
    
    // Use:
    #[Watch('email', handler: EmailHandler::class)]
    #[Watch('status', handler: StatusHandler::class)]
    
  2. Timing Strategies:

    • Before Save: Validate or transform data pre-persistence.
      #[Watch('price', handler: PriceValidator::class, timing: 'before')]
      
    • After Save: Trigger async jobs or notifications.
      #[Watch('status', handler: SendNotificationJob::class, timing: 'after', queue: 'high')]
      
  3. Queueable Handlers: Offload heavy work to queues:

    #[Watch('content', handler: ProcessContentJob::class, queue: 'long')]
    public string $content;
    
  4. Dynamic Watchers: Register watchers programmatically (e.g., for polymorphic models):

    $model->watch('dynamic_column', DynamicHandler::class);
    

Integration Tips

  • Leverage ColumnChange: Access old/new values, model instance, and metadata:

    public function handle(ColumnChange $change): void
    {
        $change->model;       // Updated model
        $change->column;      // 'email'
        $change->oldValue;    // Previous value
        $change->newValue;    // New value
    }
    
  • Combine with Events: Use watchers for side effects and events for broader reactivity:

    #[Watch('published_at', handler: ArchiveOldContentJob::class)]
    public ?Carbon $published_at;
    
    // In model:
    protected $dispatchesEvents = [
        'updated' => [ContentUpdated::class],
    ];
    
  • Testing: Mock handlers or use ColumnWatcher::assertHandled():

    $this->assertHandled(User::class, 'email', EmailUpdatedHandler::class);
    

Gotchas and Tips

Pitfalls

  1. Infinite Loop Protection:

    • The package auto-detects loops (e.g., handler updating the same column).
    • Fix: Use ignoreLoopProtection sparingly or implement custom logic:
      #[Watch('recursive_field', handler: RecursiveHandler::class, ignoreLoopProtection: true)]
      
  2. Timing Conflicts:

    • before handlers run before model events (e.g., saving).
    • after handlers run after saved but before updated.
    • Tip: Use Model::fireModelEvent('saving') in before handlers to trigger custom events.
  3. Queue Delays:

    • Async handlers may not reflect immediate DB state.
    • Workaround: Pass the ColumnChange object to the job for consistency.
  4. Mass Assignment:

    • Watchers trigger on explicit assignments (e.g., $model->email = 'test').
    • Gotcha: fill() or update() may bypass watchers if columns aren’t in $fillable.
    • Fix: Use #[Watch(..., trigger: 'always')] or override fill():
      public function fill(array $attributes = []): static
      {
          $this->fireColumnWatchersBeforeFill($attributes);
          return parent::fill($attributes);
      }
      

Debugging

  • Enable Logging: Set debug: true in config/column-watcher.php to log handler execution.
  • Check Handler Execution: Use ColumnWatcher::handlers() to list registered watchers:
    dd(ColumnWatcher::handlers(User::class));
    
  • Test with ColumnChange: Manually trigger watchers in tests:
    $change = new ColumnChange(User::class, 'email', 'old@example.com', 'new@example.com');
    $handler = app(EmailUpdatedHandler::class);
    $handler->handle($change);
    

Extension Points

  1. Custom Handlers: Implement Ascend\ColumnWatcher\Contracts\Handler for reusable logic:

    class LogColumnChange implements Handler
    {
        public function handle(ColumnChange $change): void
        {
            Log::info("Column {$change->column} changed", ['model' => $change->model]);
        }
    }
    
  2. Dynamic Conditions: Use #[Watch(..., condition: fn($old, $new) => ...)] for conditional triggers:

    #[Watch('status', handler: NotifyAdminJob::class, condition: fn($old, $new) => $new === 'banned')]
    
  3. Global Watchers: Register watchers for all models via service provider:

    ColumnWatcher::watchAllModels('global_column', GlobalHandler::class);
    
  4. Override Defaults: Extend the ColumnWatcher facade to add custom methods:

    // app/Providers/ColumnWatcherServiceProvider.php
    ColumnWatcher::extend(function ($watcher) {
        $watcher->addMacro('log', function ($column) {
            $this->watch($column, LogColumnChange::class);
        });
    });
    

    Usage:

    $model->log('audit_column');
    
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.
sentix/ai-chatbot
terminal42/code-quality-tools
codifyo/ts-generator-bundle
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