by zackaj
Laravel-debounce allows you to accumulate / debounce a job, notification or command to avoid spamming your users and your app's queue.
It also tracks and registers every request occurrence and gives you a nice report tracking with information like ip address and authenticated user per request.
This laravel package uses UniqueJobs (atomic locks) and caching to run only one instance of a task in a debounced interval of x seconds delay.
Everytime a new activity is recorded (occurrence), the execution is delayed by x seconds.
- Debounce Notifications, Jobs and Artisan Commands Basic usage & Advanced usage
- Report Tracking
- Bonus CLI Debounce
A debounced notification to bulk notify users about new uploaded files.
debounce_compressed.mp4
See Code
FileUploaded.php
<?php namespace App\Notifications; use App\Models\File; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; class FileUploaded extends Notification { use Queueable; public function __construct(public File $file) {} public function via(object $notifiable): array { return ['database']; } public function toArray(object $notifiable): array { return [ 'files' => $this->file->user->files() ->where('created_at', '>=', $this->file->created_at) ->get(), ]; } }DemoController.php
<?php namespace App\Http\Controllers; use App\Models\File; use App\Models\User; use App\Notifications\FileUploaded; use Illuminate\Http\Request; use Illuminate\Support\Facades\Notification; use Zackaj\LaravelDebounce\Facades\Debounce; class DemoController extends Controller { public function normalNotification(Request $request) { $user = $request->user(); $file = File::factory()->create(['user_id' => $user->id]); $otherUsers = User::query()->whereNot('id', $user->id)->get(); Notification::send($otherUsers, new FileUploaded($file)); return back(); } public function debounceNotification(Request $request) { $user = $request->user(); $file = File::factory()->create(['user_id' => $user->id]); $otherUsers = User::query()->whereNot('id', $user->id)->get(); Debounce::notification( notifiables: $otherUsers, notification:new FileUploaded($file), delay: 5, uniqueKey:$user->id, ); return back(); } }- Laravel application (> 10.x)
- Up and running cache system that supports atomic locks
- Up and running queue worker
composer require zackaj/laravel-debounceYou can debounce existing jobs, notifications and commands with zero setup.
Warning you can't access report tracking without extending the package's classes, see Advanced usage.
use Zackaj\LaravelDebounce\Facades\Debounce; //job Debounce::job( job:new Job(),//replace delay:5,//delay in seconds uniqueKey:auth()->user()->id,//debounce per Job class name + uniqueKey sync:false, //optional, job will be fired to the queue ); //notification Debounce::notification( notifiables: auth()->user(), notification: new Notification(),//replace delay: 5, uniqueKey: auth()->user()->id, sendNow: false, ); //command Debounce::command( command: new Command(),//replace delay: 5, uniqueKey: $request->ip(), parameters: ['name' => 'zackaj'],//see Artisan::call() signature toQueue: false,//optional, send command to the queue when executed outputBuffer: null,//optional, //see Artisan::call() signature );In order to use:
your existing jobs, notifications and commands must extend:
use Zackaj\LaravelDebounce\DebounceJob; use Zackaj\LaravelDebounce\DebounceNotification; use Zackaj\LaravelDebounce\DebounceCommand;or just generate new ones using the available make commands.
- Notification
php artisan make:debounce-notification TestNotification- Job
php artisan make:debounce-job TestJob- Command
php artisan make:debounce-command TestCommandAlternatively, now you can debounce from the job, notification and command instances directly without using the Debounce facade used in Basic usage
(new Job())->debounce(...); (new Notification())->debounce(...); (new Command())->debounce(...);Laravel-debounce uses the cache to store every request occurrence, use getReport() method within your debounceables to access the report chain that has a collection of occurrences.
Every report will have one occurrence minimum.
<?php namespace App\Jobs; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Zackaj\LaravelDebounce\DebounceJob; class Jobless extends DebounceJob implements ShouldQueue { use Dispatchable; public function handle(): void { $this->getReport()->occurrences;//collection of occurrences $this->getReport()->occurrences->count(); $this->getReport()->occurrences->first()->happenedAt; $this->getReport()->occurrences->first()->ip; $this->getReport()->occurrences->first()->ips; $this->getReport()->occurrences->first()->requestHeaders;//HeaderBag $this->getReport()->occurrences->first()->user;//authenticated user | null } }If you wish to run some code before and/or after firing the debounceables you can use the available hooks.
Important: after() hook could run before your debounceable is handled if it's sent to the queue when:
sendNow==falseand your notificationimplements ShouldQueuesync==falseand your jobimplements ShouldQueuetoQueue==true(command)
see: Basic usage
<?php ... class Jobless extends DebounceJob implements ShouldQueue { ... public function before(): void { //run before dispatching the job } public function after(): void { //run after dispatching the job } }You get the $notifiables injected into the hooks.
<?php ... class FileUploaded extends DebounceNotification { ... public function before($notifiables): void { //run before sending the notification } public function after($notifiables): void { //run after sending the notification } }Due to limitations, the hook methods must be static.
<?php ... class Test extends DebounceCommand { ... public static function before(): void { //run before executing the command } public static function after(): void { //run after executing the command } }By default laravel-debounce debounces from the last occurrence happenedAt timestamp
public function getLastActivityTimestamp(): ?Carbon { return $this->getReport()->occurrences->last()->happenedAt; }You can override this method in your debounceables in order to debounce from a custom timestamp of your choice. If null is returned the debouncer will fallback to the default implementation above.
<?php ... class Jobless extends DebounceJob implements ShouldQueue { ... public function getLastActivityTimestamp(): ?Carbon { return Message::latest()->first()?->seen_at; } }You get the $notifiables injected into the method.
<?php ... class FileUploaded extends DebounceNotification { ... public function getLastActivityTimestamp(mixed $notifiables): ?Carbon { return $this->file->user->files->latest()->first()?->created_at; } }Due to limitations, the method must be static.
<?php ... class Test extends DebounceCommand { ... public static function getLastActivityTimestamp(): ?Carbon { return User::latest()->first()?->created_at; } }For fun, you can actually debounce commands from the CLI using the debounce:command Artisan command.
php artisan debounce:command 5 uniqueKey app:testhere's the signature for the command: php artisan debounce:command {delay} {uniqueKey} {signature*}
I recommend using Laravel telescope to see the debouncer live in the queues tab and to debug any failures.
- Unique lock gets stuck sometimes when jobs fail github issue, I made a fix to the laravel core framework about this give it a reaction: PR (merged)
- cause: this happens when deleted models are unserialized causing the job to fail without clearing the lock.
- solution: don't use
SerializesModelstrait on Notifications/Jobs. (old temporary solution, now the bug is fixed)
Contributions, issues and suggestions are always welcome! See contributing.md for ways to get started.
