Model events fired multiple times #1251
Labels
accepted
Issues that have been accepted by the maintainers for inclusion
maintenance
PRs that fix bugs, are translation changes or make only minor changes
needs pr
Issues that are awaiting a PR to be submitted
Milestone
Winter CMS Build
1.2
PHP Version
8.1
Database engine
MySQL/MariaDB
Plugins installed
No response
Issue description
Note: I will use
afterSave
as an example, but this issue applies to other events as well.When an
afterSave()
method is defined in a model, each time the save action is run,afterSave()
is bound to themodel.afterSave
event, and thenmodel.afterSave
event is fired.Here is the code I am talking about: https://github.com/wintercms/storm/blob/54a0814cc44ac0fd2572e0da5f1dbab504979871/src/Database/Model.php#L227-L237
This means that if we call
save()
twice on the same model instance,afterSave()
will be run once on the first time, which is expected, but twice on the second time (3 times in total).I was about to do a PR replacing this
bindEvent
bybindEventOnce
to solve the problem, but it may not be the best option.Using
bindEventOnce
would fix this issue in most cases, but there's one I can think of where it would fail: if someone registers a handler formodel.afterSave
with more priority than the default, and this handler sometimes returnsfalse
.In this case, let's imagine we save the model a first time: we bind the
afterSave()
method once; we fire the event; it gets cancelled by the other handler; so we don't call theafterSave()
method.Then we save a second time: we bind
afterSave()
again (we now have 2); we fire the event; for some reason the other handler does not return false; we callafterSave()
twice.Maybe a better approach would be to bind the
afterSave()
method tomodel.afterSave
on every model instance on construction (i.e. taking it out ofself::$eventMethod(...)
), but it would require a bit more rewriting.As is, it won't work because of the exit condition here. We would need to run
bindEvent
on every model instance, butself::$eventMethod
only once per model class.There would be one side-effect thoug (I guess it's an undefined behavior we can change, but I mention it just in case): the
afterSave()
method would be bound early in the object construction instead of at the time the save is made. If someone registers anothermodel.afterSave
handler with default priority, the order in which the handlers are called may change.Let me know what you prefer. My initial guess is that using
bindEventOnce
requires less change and would mostly fix the issue without altering existing behavior; the other solution is less hacky but requires more changes and testing. Or maybe you have other ideas ?Let me know if you would like a PR.
Steps to replicate
Using a model like this :
Demonstration using
artisan tinker
:Workaround
Option 1: As only the
afterSave
model method is affected,TestModel
can be adapted like this:Result using
artisan tinker
:Option 2: fetch a new instance of the model between saves
The text was updated successfully, but these errors were encountered: