Use of getOriginal() on model in observer doesn't return original value, when using transactions and ShouldHandleEventsAfterCommit. #52552
Replies: 3 comments
-
It seems that you're dealing with a situation where the model object being tracked within a transaction is modified, which affects the behavior of the callbacks assigned to that transaction. This can lead to issues, especially when working with models in the context of a transaction, particularly in conjunction with events like updated. In short, when a transaction is registered, Laravel creates callbacks that will be executed after the transaction is committed. If the model is modified during the transaction and Laravel uses the same object for the registered callbacks, this can lead to a scenario where the callbacks operate on outdated or inconsistent data. In PHP, objects are passed by reference, meaning that if you modify an object in one place, those changes will be reflected everywhere that object is used unless you specifically clone the object before making changes. |
Beta Was this translation helpful? Give feedback.
-
This is a big issue when building a logging mechanism for registering model changes, imagine this pretty common example: Model to watch class Order extends Model{
protected $fillable = [
'state_id', // ...
];
} Model to log changes #[ObservedBy(OrderObserver::class)]
class OrderStates extends Model{
protected $fillable = [
'order_id', 'old_state_id', 'new_state_id', // ...
];
public function order(): BelongsTo
{
return $this->belongsTo(Order::class, 'order_id', 'id');
}
} Observer code class OrderObserver implements ShouldHandleEventsAfterCommit
{
public function updated(Order $model) // tried also updating with no luck
{
$new_state_id = $model->state_id;
$previous_state_id = $model->getOriginal('state_id'));
// here I would like to log the change in a OrderStates entry
// but real previous state_id is lost when wrapping the change in a transaction...
}
} Example code DB::beginTransaction;
$order = Order::find(1);
$order->update(['state_id' => 2]);
// other relevant code...
DB::commit(); Is there any other clean way to do this? |
Beta Was this translation helpful? Give feedback.
-
Solution
Thank you for this. We had thoughts of using this ShouldHandleEventsAfterCommit but we never used it. Lucky us. |
Beta Was this translation helpful? Give feedback.
-
Laravel Version
11.20
PHP Version
8.3
Database Driver & Version
sqlite
Description
When using and observer to subscribe to
updated
events on models, you can access the original value in a few different ways, including thegetOriginal(...)
method.If the observer implements the
ShouldHandleEventsAfterCommit
and the changes to the model is wrapped in a databas transaction, the events should be processed after the transaction has commited. This works as expected.However. When using transactions and
ShouldHandleEventsAfterCommit
thegetOriginal(...)
method no longer returns the original value.I did not expect this, and can't find anything about it in the documentation. Is this a bug?
Steps To Reproduce
Below are test cases for this. It consists of:
updated
, asserts that the value of$model->test
isupdated
and the value of$model->getOriginal('test')
isoriginal
test
set tooriginal
and then updates that field toupdated
.I would expect both of these test cases to pass, but the test case that uses transactions fails.
Beta Was this translation helpful? Give feedback.
All reactions