This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the object-oriented-programming category.
Last Updated: 2024-11-21
I had an infinite loop bug when using an observer connected to the updated
callback in an eloquent ORM mode (for an Advisor
- which corresponds to a "tax advisor" in real life).
<?php
class AdvisorObserver
public function updated(Advisor $advisor)
{
// Strip non-digits from phone/fax
$advisor->phone = preg_replace('~\D~', '', $advisor->phone);
$advisor->save();
UpdateAdvisorGeocoordinates::dispatch($advisor);
}
}
// Wiring up the observer
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Advisor::observe(AdvisorObserver::class);
}
}
The reason this went on an infinite loop was because I called save()
in the
updated()
callback, which triggered this exact same code again.
I could have avoided this:
isDirty(["phone", "fax"])
- that way there
would be a modification (and a save) after the first iteration, but not the second.saving
, with the expectation that the modified data would be saved
during the later call to save()
in the lifecycle. But be careful here: if I
were to also dispatch
the UpdateAdvisorGeocoordinates
work to the queue
in this saving
method, then it would receive a stale version of the data
without these (as of the moment) unsaved changes. Better to retain the
dispatching to the queue in the updated
method. <?php
Advisor::withoutEvents(function () {
$advisor->save();
});
Another complication cropped up when code that normally occurs in a background queue is instead executed in-line (i.e. synchronously), as might happen in a unit testing environments. Since, by their nature, a background job has to save its changes, this can cause unexpected test failures and infinite loops.