🕵️ SicherheitslückenCVE-2026-58728 | Google Android ARM64 MMU mmu.h ARM64_TLBI race condition(15.09.2026 um 21:40 Uhr)
🔧 ProgrammierungEnforce GitHub Advanced Security configurations(15.09.2026 um 21:31 Uhr)
🔧 ProgrammierungHow i give my coding agent a map of the repo with Empryo(15.09.2026 um 21:54 Uhr)
🔧 ProgrammierungReef Connects Agent Feedback, Learning and Versioned Delivery(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungUAJY Handbook RAG Chatbot Splits FAISS Search From Gemini(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungKnowledge Base For AI Agents: What It Must Do(15.09.2026 um 22:00 Uhr)
🕵️ SicherheitslückenCVE-2026-58728 | Google Android ARM64 MMU mmu.h ARM64_TLBI race condition(15.09.2026 um 21:40 Uhr)
🔧 ProgrammierungEnforce GitHub Advanced Security configurations(15.09.2026 um 21:31 Uhr)
🔧 ProgrammierungHow i give my coding agent a map of the repo with Empryo(15.09.2026 um 21:54 Uhr)
🔧 ProgrammierungReef Connects Agent Feedback, Learning and Versioned Delivery(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungUAJY Handbook RAG Chatbot Splits FAISS Search From Gemini(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungKnowledge Base For AI Agents: What It Must Do(15.09.2026 um 22:00 Uhr)

🔧 Programmierung 🕛 vor 7 Monaten 12 Min Lesezeit
0

Re-thinking Laravel's Observer Pattern

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Let me tell you about the observer that broke me.



It started innocently. A client needed an email sent when a user's account status changed. Easy - I'll use an observer:




CODE
class UserObserver
{
public function updated(User $user)
{
if ($user->wasChanged('status')) {
Mail::to($user)->send(new AccountStatusChanged($user));
}
}
}






Clean. Simple. Laravel best practices.



Then the requirements kept coming.






The Observer That Ate My Codebase



"Can we also sync status changes to Salesforce?"




CODE
public function updated(User $user)
{
if ($user->wasChanged('status')) {
Mail::to($user)->send(new AccountStatusChanged($user));

// New requirement
app(SalesforceClient::class)->updateContact($user->salesforce_id, [
'status' => $user->status,
]);
}
}






"We need to update the user's Stripe subscription when their plan changes."




CODE
public function updated(User $user)
{
if ($user->wasChanged('status')) {
Mail::to($user)->send(new AccountStatusChanged($user));
app(SalesforceClient::class)->updateContact($user->salesforce_id, [
'status' => $user->status,
]);
}

// Another requirement
if ($user->wasChanged('plan')) {
app(StripeClient::class)->updateSubscription(
$user->stripe_subscription_id,
['price' => $user->plan->stripe_price_id]
);
}
}






"The marketing team needs to know when emails change for their mailing list."



"Legal wants an audit log of all profile changes."



"Can we notify the account manager when a high-value customer changes anything?"



Six months later:




CODE
class UserObserver
{
public function __construct(
private SalesforceClient $salesforce,
private StripeClient $stripe,
private MailingListService $mailingList,
private AuditLogger $auditLogger,
private SlackNotifier $slack,
private AnalyticsService $analytics,
) {}

public function updated(User $user)
{
if ($user->wasChanged('status')) {
Mail::to($user)->send(new AccountStatusChanged($user));
$this->salesforce->updateContact($user->salesforce_id, ['status' => $user->status]);
$this->auditLogger->log('status_change', $user, $user->getOriginal('status'), $user->status);

if ($user->status === 'churned') {
$this->slack->notify('#customer-success', "Customer {$user->name} churned");
$this->analytics->track('customer_churned', $user);
}

if ($user->status === 'active' && $user->getOriginal('status') === 'trial') {
$this->analytics->track('trial_converted', $user);
}
}

if ($user->wasChanged('plan')) {
$this->stripe->updateSubscription($user->stripe_subscription_id, [
'price' => $user->plan->stripe_price_id,
]);
$this->auditLogger->log('plan_change', $user, $user->getOriginal('plan_id'), $user->plan_id);
$this->salesforce->updateContact($user->salesforce_id, ['plan' => $user->plan->name]);

if ($user->plan->price > $user->getOriginal('plan')->price) {
$this->slack->notify('#sales', "Upgrade: {$user->name} moved to {$user->plan->name}");
}
}

if ($user->wasChanged('email')) {
$this->mailingList->updateSubscriber($user->getOriginal('email'), $user->email);
Mail::to($user->getOriginal('email'))->send(new EmailChangedNotification($user));
$this->auditLogger->log('email_change', $user, $user->getOriginal('email'), $user->email);
}

if ($user->wasChanged(['name', 'phone', 'company'])) {
$this->salesforce->updateContact($user->salesforce_id, $user->only(['name', 'phone', 'company']));
}

if ($user->isHighValue() && $user->isDirty()) {
$this->slack->notify('#account-managers', "High-value customer {$user->name} updated their profile");
}

// ... 150 more lines
}
}






This is a real pattern. Maybe you recognise it.









The Five Problems With Observers






Problem 1: The God Class



That observer now has six dependencies, handles five different concerns, and runs every time any user field changes.



Single Responsibility Principle? We abandoned that somewhere around line 50.



Want to understand what happens when a user's email changes? Hope you enjoy reading 200 lines of nested conditionals.






Problem 2: The Testing Nightmare



How do you test that status changes send an email without also testing Salesforce, Stripe, Slack, and the audit log?




CODE
public function test_status_change_sends_email()
{
// Mock EVERYTHING
Mail::fake();
$this->mock(SalesforceClient::class);
$this->mock(StripeClient::class);
$this->mock(MailingListService::class);
$this->mock(AuditLogger::class);
$this->mock(SlackNotifier::class);
$this->mock(AnalyticsService::class);

$user = User::factory()->create(['status' => 'pending']);
$user->update(['status' => 'active']);

Mail::assertSent(AccountStatusChanged::class);
}






You're mocking six services to test one behaviour. And if someone adds a seventh dependency? Every test breaks.



Alternatively, you disable the observer entirely in tests - which means you're not testing real application behaviour.






Problem 3: Observers Aren't Queueable



That Salesforce API call? That Stripe sync? They're running synchronously in your user's request.



"Just dispatch a job from the observer," you say. Sure:




CODE
if ($user->wasChanged('status')) {
Mail::to($user)->send(new AccountStatusChanged($user));
SyncStatusToSalesforce::dispatch($user, $user->getOriginal('status'), $user->status);
LogStatusChange::dispatch($user, $user->getOriginal('status'), $user->status);

if ($user->status === 'churned') {
NotifySlackOfChurn::dispatch($user);
TrackChurnAnalytics::dispatch($user);
}
}






Now you have an observer that dispatches jobs, plus separate job classes, plus you're manually passing old/new values everywhere because the job runs later and can't access getOriginal().



The observer has become a dispatcher for the real logic that lives elsewhere.






Problem 4: Hidden Side Effects



Where are observers registered? In AppServiceProvider or EventServiceProvider:




CODE
public function boot()
{
User::observe(UserObserver::class);
Order::observe(OrderObserver::class);
Payment::observe(PaymentObserver::class);
// ... 20 more
}






A new developer opens the User model. They see properties, relationships, scopes. Nothing indicates that saving this model triggers a cascade of external API calls.



The side effects are invisible at the point where they matter most.






Problem 5: No Granular Control



Observers hook into model events: creating, created, updating, updated, saving, saved, deleting, deleted.



But your logic isn't "when user is updated" - it's "when user's status is updated" or "when user's email is updated."



So every handler starts with:




CODE
if ($user->wasChanged('status')) {






You're checking for column changes manually, every time, in every method.









What If There Was A Better Way?



I wanted something that:




  1. Declared watchers on the model so side effects are visible

  2. Triggered on specific columns, not all updates

  3. Was queueable without manual job dispatching

  4. Could be faked individually in tests

  5. Automatically tracked old and new values



So I built it.









Introducing Laravel Column Watcher



GitHub:



Star it, fork it, open issues, or just tell me I'm wrong about observers. I want to hear it all.




CODE
composer require ascend/laravel-column-watcher









Thanks for reading. If this solved a problem you've had, share it with someone who's drowning in observer spaghetti.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
CVE-2026-58728 | Google Android ARM64 MMU mmu.h ARM64_TLBI race condition
1 Quelle
DFN-CERT-2026-4853 Xcode: Eine Schwachstelle ermöglicht das Ausspähen von Informationen
1 Quelle
Enforce GitHub Advanced Security configurations
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Re-thinking Laravel's Observer Pattern

Thematisch verwandte Begriffe: Rethinking, Laravels, Observer, Pattern · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...