fix(events): stop double-registering transaction listeners

Laravel's event discovery is enabled by default (the base EventServiceProvider
sets $shouldDiscoverEvents = true and this app has no subclass to override it),
so every listener in app/Listeners is already wired to its event by its handle()
type-hint. AppServiceProvider::boot() was ALSO registering six of them with
Event::listen, so each was registered twice and every queued listener published
its job twice per event.

For POST /transactions this meant AssignTransactionToBudget and
CategorizeTransactionWithAi each inserted two rows into the jobs table per
created transaction (Sentry PHP-LARAVEL-3V, "N+1 insert into jobs"), and
PostStripeEventToDiscord posted to Discord twice per webhook.

Drop the redundant explicit registrations and rely on discovery, which already
wires all listeners exactly once (ScheduleDripEmailsListener and
SyncUserToResendListener were discovery-only already).

Fixes PHP-LARAVEL-3V
This commit is contained in:
Víctor Falcón 2026-07-01 10:10:20 +02:00
parent 10442c1e32
commit 26790990f3
1 changed files with 5 additions and 20 deletions

View File

@ -3,29 +3,17 @@
namespace App\Providers;
use App\Contracts\BankingProviderInterface;
use App\Events\TransactionCreated;
use App\Events\TransactionDeleted;
use App\Events\TransactionUpdated;
use App\Http\Responses\RegisterResponse;
use App\Listeners\ApplyAutomationRules;
use App\Listeners\AssignTransactionToBudget;
use App\Listeners\CategorizeTransactionWithAi;
use App\Listeners\PostStripeEventToDiscord;
use App\Listeners\UnassignTransactionFromBudget;
use App\Listeners\UpdateLastLoggedInAt;
use App\Services\Ai\Contracts\RuleSuggestionGenerator;
use App\Services\Ai\Contracts\TransactionMatcher;
use App\Services\Ai\LaravelAiRuleSuggestionGenerator;
use App\Services\Ai\UncategorizedTransactionMatcher;
use App\Services\Banking\EnableBankingProvider;
use App\Services\Discord\DiscordWebhook;
use Illuminate\Auth\Events\Login;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Cashier\Cashier;
use Laravel\Cashier\Events\WebhookReceived;
use Laravel\Fortify\Contracts\RegisterResponse as RegisterResponseContract;
class AppServiceProvider extends ServiceProvider
@ -59,14 +47,11 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
Event::listen(TransactionCreated::class, ApplyAutomationRules::class);
Event::listen(TransactionCreated::class, AssignTransactionToBudget::class);
Event::listen(TransactionCreated::class, CategorizeTransactionWithAi::class);
Event::listen(TransactionUpdated::class, AssignTransactionToBudget::class);
Event::listen(TransactionDeleted::class, UnassignTransactionFromBudget::class);
Event::listen(WebhookReceived::class, PostStripeEventToDiscord::class);
Event::listen(Login::class, UpdateLastLoggedInAt::class);
// Event listeners are registered automatically via Laravel's event
// discovery of the App\Listeners directory (matched by their handle()
// type-hint). Do not also register them explicitly here — doing both
// registers every listener twice, so each queued listener is dispatched
// twice per event.
RateLimiter::for('emails', function (object $job): Limit {
return Limit::perSecond(30);
});