Filterable
Home
๐Ÿ“ฆ Installation
  • Setting Up Filterable
  • Discover Command
  • Listing All Filters
  • Testing Filters
  • Inspecting Filterable Classes
GitHub
Home
๐Ÿ“ฆ Installation
  • Setting Up Filterable
  • Discover Command
  • Listing All Filters
  • Testing Filters
  • Inspecting Filterable Classes
GitHub
  • Home
  • Introduction
  • Installation
  • Service Provider
  • How It Works
  • Engines

    • Invokable
    • Tree
    • Ruleset
    • Expression
  • Features

    • Header-Driven Filter Mode
    • Auto Register Filterable Macro
    • Conditional Logic
    • Filter Aliases
    • Through callbacks
    • Auto Binding
    • Custom engines
    • Data Provisioning
  • Execution

    • Invoker
  • API

    • Filterable
    • Filterable facade
    • Payload
    • Sorter
  • CLI

    • Setup Filterable
    • Discover Filters
    • Test Filter
    • List Filters
    • Inspect Filter
  • Event System
  • Profile Management
  • Profiler
  • Sorting
  • Authorization
  • Validation
  • Sanitization

Events

The Filterable Event System allows you to listen to lifecycle events during filtering operations. This provides powerful hooks for logging, monitoring, analytics, auditing, and implementing custom business logic that reacts to filtering activities.

Table of Contents

  • Introduction
  • Configuration
  • Available Events
  • Registering Event Listeners
    • Global Listeners
    • Filter-Specific Observers
  • Event Payloads
  • Enabling/Disabling Events
  • Use Cases
  • Exception Handling
  • API Reference

Introduction

The event system is lightweight, framework-agnostic (though designed for Laravel), and doesn't depend on Laravel's Event facade. It uses a simple pub-sub pattern that integrates seamlessly with the filterable lifecycle.

Key Features:

  • ๐ŸŽฏ Global and filter-specific event listeners
  • ๐Ÿ›ก๏ธ Safe exception handling (listener failures won't crash your app)
  • โš™๏ธ Configurable (can be disabled globally or per instance)
  • ๐Ÿ“Š Perfect for logging, monitoring, and analytics
  • ๐Ÿงช Easy to test with listener flushing

Configuration

Enable or disable the event system in config/filterable.php:

'events' => [
    /*
    |--------------------------------------------------------------------------
    | Enable or Disable Event System
    |--------------------------------------------------------------------------
    |
    | This option allows you to enable or disable the event system globally.
    | When disabled, no event listeners or observers will be triggered.
    |
    */
    'enabled' => env('FILTERABLE_EVENTS_ENABLED', true),
],

You can also set this in your .env file:

FILTERABLE_EVENTS_ENABLED=true

Available Events

The following events are dispatched during the filterable lifecycle:

Event NameDescriptionWhen FiredPayload
filterable.initializingA new Filterable instance is being createdConstructor start$filterable
filterable.resolvedEngine and request data have been resolvedConstructor end$filterable, $engine, $data
filterable.appliedFilters have been executed successfullyAfter successful apply()$filterable, $builder
filterable.failedAn exception occurred during apply()Catch block in apply()$filterable, $exception, $builder
filterable.finishedFiltering lifecycle has completedFinally block in apply()$filterable, $builder

Registering Event Listeners

Global Listeners

Global listeners are triggered for all filterable instances, regardless of the filter class.

use Kettasoft\Filterable\Filterable;

Filterable::on('filterable.applied', function (Filterable $filterable) {
    logger()->info("Filter applied", [
        'filter_class' => get_class($filterable),
        'sql' => $filterable->getBuilder()->toSql(),
        'bindings' => $filterable->getBuilder()->getBindings(),
    ]);
});

Registering Multiple Listeners:

// Log when filters start initializing
Filterable::on('filterable.initializing', function (Filterable $filterable) {
    logger()->debug("Initializing filter: " . get_class($filterable));
});

// Track successful applications
Filterable::on('filterable.applied', function (Filterable $filterable) {
    metrics()->increment('filters.applied');
});

// Handle failures
Filterable::on('filterable.failed', function (Filterable $filterable, Throwable $exception) {
    logger()->error("Filter failed", [
        'filter' => get_class($filterable),
        'error' => $exception->getMessage(),
    ]);
});

Filter-Specific Observers

Observers are called only for specific filter classes. This is ideal for filter-specific logging or side effects.

use App\Http\Filters\PostFilter;
use Kettasoft\Filterable\Filterable;
use Kettasoft\Filterable\Foundation\Events\FilterableState;

Filterable::observe(PostFilter::class, function (FilterableState $event, Filterable $filterable) {
    // $event is the event name without 'filterable.' prefix
    // $filterable is instance of Filterable

    if ($event->is('applied')) {
        activity()
            ->causedBy(auth()->user())
            ->performedOn($filterable->getModel())
            ->log('PostFilter was applied');
    }
});

Multiple Observers:

Filterable::observe(UserFilter::class, function (string $event, Filterable $filterable) {
    match ($event) {
        'initializing' => logger()->info("UserFilter initializing"),
        'applied' => logger()->info("UserFilter applied successfully"),
        'failed' => logger()->error("UserFilter failed", ['error' => $filterable->getMessage()]),
        default => null,
    };
});

Event Payloads

Each event receives different payload data:

filterable.initializing

function (Filterable $filterable) {
    // $filterable: The Filterable instance
}

filterable.resolved

function ($engine, $data) {
    // $engine: The resolved Engine instance
    // $data: Parsed request data array
}

filterable.applied

function (Filterable $filterable) {
    // $filterable: The Filterable instance
}

filterable.failed

function ($filterable, $exception) {
    // $filterable: The Filterable instance
    // $exception: The Throwable that was caught
}

filterable.finished

function (Filterable $filterable) {
    // $filterable: The Filterable instance
}

Enabling/Disabling Events

Global Configuration

Disable events globally in config/filterable.php:

'events' => [
    'enabled' => false,
],

Per-Instance Control

Override the global setting for specific instances:

// Disable events for this instance
$filter = PostFilter::create()->disableEvents();

// Enable events for this instance (even if globally disabled)
$filter = PostFilter::create()->enableEvents();

Conditional Event Control

$filter = PostFilter::create()
    ->when(app()->environment('production'), fn($f) => $f->disableEvents())
    ->apply($builder);

Use Cases

1. Audit Logging

Track who applied which filters and when:

Filterable::on('filterable.applied', function (Filterable $filterable) {
    AuditLog::create([
        'user_id' => auth()->id(),
        'filter_class' => get_class($filterable),
        'filters_applied' => $filterable->getData(),
        'sql_query' => $filterable->getBuilder()->toSql(),
        'timestamp' => now(),
    ]);
});

2. Performance Monitoring

Track slow filters:

Filterable::on('filterable.finished', function (Filterable $filterable) {
    $executionTime = microtime(true) - LARAVEL_START;

    if ($executionTime > 1.0) {
        logger()->warning("Slow filter detected", [
            'filter' => get_class($filterable),
            'execution_time' => $executionTime,
            'sql' => $filterable->getBuilder()->toSql(),
        ]);
    }
});

3. Analytics & Metrics

Collect usage statistics:

Filterable::on('filterable.applied', function (Filterable $filterable) {
    Redis::hincrby('filter_stats', get_class($filterable), 1);

    $data = $filterable->getData();
    foreach (array_keys($data) as $field) {
        Redis::hincrby('filter_fields', $field, 1);
    }
});

4. Error Notifications

Send alerts when filters fail:

Filterable::on('filterable.failed', function (Filterable $filterable, Throwable $exception) {
    Notification::route('slack', config('logging.slack_webhook'))
        ->notify(new FilterFailureNotification(
            get_class($filterable),
            $exception->getMessage(),
            $filterable->getData()
        ));
});

5. Cache Invalidation

Clear relevant caches when filters are applied:

Filterable::observe(PostFilter::class, function ($event, Filterable $filterable) {
    if ($event === 'applied') {
        Cache::tags(['posts', 'filters'])->flush();
    }
});

6. Development Debugging

Log all filter activity in development:

if (app()->environment('local')) {
    Filterable::on('filterable.resolved', function ($engine, $data) {
        logger()->debug("Filter Resolved", [
            'engine' => get_class($engine),
            'data' => $data,
        ]);
    });
}

Exception Handling

The event system handles exceptions gracefully. If a listener throws an exception, it will be caught and logged without breaking the filtering process.

Filterable::on('filterable.applied', function (Filterable $filterable) {
    // This will be caught and logged, but won't crash the app
    throw new \Exception("Listener failed!");
});

// The filter will still work correctly
$results = PostFilter::create()->apply($builder)->get();

Exception Logging:

Failed listeners are logged using Laravel's logger (if available) or error_log():

[2025-10-14 10:23:45] production.ERROR: Filterable event listener failed for event 'filterable.applied': Listener failed! {"event":"filterable.applied","type":"listener","exception":{...},"filterable_class":"App\\Http\\Filters\\PostFilter"}

API Reference

Static Methods

Filterable::on(string $event, callable $callback): void

Register a global event listener for all filterable instances.

Parameters:

  • $event: The event name (e.g., 'filterable.applied')
  • $callback: The callback to execute when the event fires

Example:

Filterable::on('filterable.applied', function (Filterable $filterable) {
    logger("Filter applied");
});

Filterable::observe(string $filterClass, callable $callback): void

Register an observer for a specific filter class.

Parameters:

  • $filterClass: The fully qualified filter class name
  • $callback: The observer callback receiving ($event, $filterable)

Example:

Filterable::observe(PostFilter::class, function ($event, Filterable $filterable) {
    if ($event === 'applied') {
        // Handle the event
    }
});

Filterable::flushListeners(): void

Remove all registered event listeners and observers.

Example:

Filterable::flushListeners();

Filterable::getListeners(string $event): array

Get all registered listeners for a specific event.

Parameters:

  • $event: The event name

Returns: Array of callable listeners

Example:

$listeners = Filterable::getListeners('filterable.applied');

Filterable::getObservers(string $filterClass): array

Get all registered observers for a specific filter class.

Parameters:

  • $filterClass: The filter class name

Returns: Array of callable observers

Example:

$observers = Filterable::getObservers(PostFilter::class);

Instance Methods

enableEvents(): static

Enable events for this specific filterable instance.

Example:

$filter = PostFilter::create()->enableEvents();

disableEvents(): static

Disable events for this specific filterable instance.

Example:

$filter = PostFilter::create()->disableEvents();

Best Practices

  1. Keep listeners lightweight: Avoid heavy processing in event listeners to prevent performance degradation.

  2. Use queued jobs for expensive operations: If you need to perform heavy tasks, dispatch a job from the listener:

    Filterable::on('filterable.applied', function ($filterable, $builder) {
        ProcessFilterAnalytics::dispatch($filterable, $builder->toSql());
    });
    
  3. Disable in production if not needed: If you're only using events for debugging, disable them in production:

    'events' => [
        'enabled' => env('FILTERABLE_EVENTS_ENABLED', !app()->environment('production')),
    ],
    
  4. Use observers for filter-specific logic: Keep global listeners for cross-cutting concerns and use observers for filter-specific behavior.

  5. Always flush in tests: Prevent test pollution by flushing listeners in tearDown():

    protected function tearDown(): void
    {
        Filterable::flushListeners();
        parent::tearDown();
    }
    
Edit this page
Last Updated:
Contributors: kettasoft
Next
Profile Management