Get expert guidance in a few days with a Laravel code review

Laravel Debounce

Laravel Debounce stats

Downloads
116
Stars
24
Open Issues
0
Forks
0

View on GitHub →

Debounce jobs ,notifications and artisan commands.

Laravel debounce

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.

Table of Contents

Introduction

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 recoreded (occurrence), the execution is delayed with x seconds.

Features

Demo

A debounced notification to bulk notify users about new uploaded files.

https://github.com/user-attachments/assets/b1d5aafd-256d-4f6f-b31a-0d6dc516793b

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();
}
}

Installation

Prerequisites

  • Laravel application (11.x , 10.x should be fine)
  • Up and running cache system that supports atomic locks
  • Up and running queue worker

Composer

composer require zackaj/laravel-debounce

Usage

Basic usage

You can debounce existing jobs, notifications and commands with zero setup.

Warning you can't access report or track the requests 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
);

Advanced usage

In order to use:

your existing jobs, notificaitons 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.

Make commands

  • Notification
php artisan make:debounce-notification TestNotification
  • Job
php artisan make:debounce-job TestJob
  • Command
php artisan make:debounce-command TestCommand

No facade usage

Alternatively, 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(...);

Report Tracking

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
}
}

Before After Hooks

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==false and your notification implements ShouldQueue
  • sync==false and your job implements ShouldQueue
  • toQueue==true (command)

see: Basic usage

Debounce job

<?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
}
}

Debounce notification

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
}
}

Debounce command

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
}
 
}

Override Timestamp

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 choice, if null returned the debouncer will fallback to the default implementation above.

Debounce job

<?php
...
class Jobless extends DebounceJob implements ShouldQueue
{
...
public function getLastActivityTimestamp(): ?Carbon
{
return Message::latest()->first()?->seen_at;
}
}

Debounce notification

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;
}
}

Debounce command

Due to limitations, the method must be static.

<?php
...
 
class Test extends DebounceCommand
{
...
public static function getLastActivityTimestamp(): ?Carbon
{
return User::latest()->first()?->created_at;
}
}

Bonus CLI Debounce

For fun, you can actually debounce commands from the CLI using debounce:command Artisan command.

php artisan debounce:command 5 uniqueKey app:test

here's the signature for the command: php artisan debounce:command {delay} {uniqueKey} {signature*}

Debugging And Monitoring

I recommend using Laravel telescope to see the debouncer live in queues tab and to debug any failures.

Known Issues

1- Unique lock gets stuck sometimes when jobs fail github issue

  • cause: this happens when deleted models are unserialized causing the job to fail without clearing the lock.

  • solution: If you're using database cache driver delete the entry from job_locks table.

Contributing

Contributions, issues and suggestions are always welcome! See contributing.md for ways to get started.

License

MIT

zackAJ photo

🕸 Laravel / Neovim enthusiast 🕸Passionate about dev tools 🕸🇩🇿🕸

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.


Zackaj Laravel Debounce Related Articles

Laravel Debounce image

Laravel Debounce

Read article
The Certification of Competence for Laravel logo

The Certification of Competence for Laravel

A community-driven, proctored assessment across 4 levels designed to validate real-world Laravel knowledge, from Junior to mastery-level Artisan. Official Vue.js, Official Nuxt, Angular, React, JS certifications also available.

The Certification of Competence for Laravel
Laravel Cloud logo

Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Cloud
Blastup logo

Blastup

Blastup provides social media enhancement services including buying Instagram likes, followers, and views, with features like instant delivery and a variety of packages to suit different needs.

Blastup
CodeKudu logo

CodeKudu

Stand-ups, Retrospectives, and 360° Feedback for the entire team. 50% off with code LARAVELNEWS.

CodeKudu
Get expert guidance in a few days with a Laravel code review logo

Get expert guidance in a few days with a Laravel code review

Expert code review! Get clear, practical feedback from two Laravel devs with 10+ years of experience helping teams build better apps.

Get expert guidance in a few days with a Laravel code review
Securing Laravel logo

Securing Laravel

The essential security resource for Laravel devs, covering everything you need to keep your apps secure. Sign up to receive weekly security tips and monthly in depth articles, diving deep into security concepts you need to know!

Securing Laravel