Skip to content

Commit 564f123

Browse files
author
Pantea Marius-ciclistu
committed
POC for laravel/framework#31778 cover changes in created updated saved that got into $original but not in db
1 parent 0279e60 commit 564f123

File tree

2 files changed

+37
-29
lines changed

2 files changed

+37
-29
lines changed

illuminate/Database/Eloquent/Concerns/HasAttributes.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ trait HasAttributes
7070
*/
7171
protected ?array $tmpDirtyIfAttributesAreSyncedFromCashedCasts = null;
7272

73+
/**
74+
* Temporary original cache to prevent changes in created,updated,saved events from getting
75+
* into $original without being saved into DB
76+
*/
77+
protected ?array $tmpOriginalBeforeAfterEvents = null;
78+
7379
/**
7480
* The attributes that should be cast.
7581
*
@@ -2002,7 +2008,7 @@ public function only($attributes)
20022008
*/
20032009
public function syncOriginal()
20042010
{
2005-
$this->original = $this->getAttributes(isset($this->tmpDirtyIfAttributesAreSyncedFromCashedCasts));
2011+
$this->original = $this->getAttributes();
20062012

20072013
return $this;
20082014
}

illuminate/Database/Eloquent/Model.php

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,10 +1159,9 @@ public function save(array $options = [])
11591159
try {
11601160
/** We will try to optimize the execution by caching $dirty array BUT,
11611161
multiple set calls will be needed (https://github.com/laravel/framework/discussions/31778)
1162-
WHEN $this->usesTimestamps() or WHEN there are listeners for the following events:
1163-
- updating event can do changes so, $this->getDirtyForUpdate() and $this->syncChanges() will call
1164-
$this->getDirty(),
1165-
- updated/saved events can do changes so, $this->syncOriginal() will call $this->getAttributes() */
1162+
WHEN $this->usesTimestamps() or WHEN there are listeners for updating event because they can do changes
1163+
so, $this->getDirtyForUpdate() and $this->syncChanges() will call $this->getDirty() which will call
1164+
getAttributes() */
11661165
if (!$this->getEventDispatcher()->hasListeners('eloquent.updating: ' . $this::class)) {
11671166
$this->tmpDirtyIfAttributesAreSyncedFromCashedCasts = $dirty;
11681167
unset($dirty);
@@ -1177,6 +1176,7 @@ public function save(array $options = [])
11771176
return false;
11781177
} finally {
11791178
$this->tmpDirtyIfAttributesAreSyncedFromCashedCasts = null;
1179+
$this->tmpOriginalBeforeAfterEvents = null;
11801180
}
11811181
}
11821182

@@ -1185,20 +1185,24 @@ public function save(array $options = [])
11851185

11861186
/** Multiple set calls are needed (https://github.com/laravel/framework/discussions/31778) because:
11871187
- $this->performInsert can do changes,
1188-
- creating event can do changes so, $this->getAttributesForInsert() will call $this->getAttributes(),
1189-
- created/saved events can do changes so, $this->syncOriginal() will call $this->getAttributes() */
1188+
- creating event can do changes so,
1189+
$this->getAttributesForInsert() and $this->syncOriginal() will call $this->getAttributes() */
11901190

1191-
$saved = $this->performInsert($query);
1191+
try {
1192+
$saved = $this->performInsert($query);
11921193

1193-
if (
1194-
'' === (string)$this->getConnectionName() &&
1195-
($connection = $query->getConnection()) instanceof Connection
1196-
) {
1197-
$this->setConnection($connection->getName());
1198-
}
1194+
if (
1195+
'' === (string)$this->getConnectionName() &&
1196+
($connection = $query->getConnection()) instanceof Connection
1197+
) {
1198+
$this->setConnection($connection->getName());
1199+
}
11991200

1200-
if ($saved) {
1201-
$this->finishSave($options + ['touch' => $isDirty]);
1201+
if ($saved) {
1202+
$this->finishSave($options + ['touch' => $isDirty]);
1203+
}
1204+
} finally {
1205+
$this->tmpOriginalBeforeAfterEvents = null;
12021206
}
12031207

12041208
return $saved;
@@ -1225,19 +1229,19 @@ public function saveOrFail(array $options = [])
12251229
*/
12261230
protected function finishSave(array $options)
12271231
{
1228-
if (
1229-
isset($this->tmpDirtyIfAttributesAreSyncedFromCashedCasts)
1230-
&& $this->getEventDispatcher()->hasListeners('eloquent.saved: ' . $this::class)
1231-
) {
1232-
$this->tmpDirtyIfAttributesAreSyncedFromCashedCasts = null;
1233-
}
1234-
12351232
$this->fireModelEvent('saved', false);
12361233

12371234
if ($options['touch'] ?? true) {
12381235
$this->touchOwners();
12391236
}
12401237

1238+
if (isset($this->tmpOriginalBeforeAfterEvents)) {
1239+
$this->original = $this->tmpOriginalBeforeAfterEvents;
1240+
$this->tmpOriginalBeforeAfterEvents = null;
1241+
1242+
return;
1243+
}
1244+
12411245
$this->syncOriginal();
12421246
}
12431247

@@ -1274,12 +1278,8 @@ protected function performUpdate(Builder $query)
12741278

12751279
$this->syncChanges();
12761280

1277-
if (
1278-
isset($this->tmpDirtyIfAttributesAreSyncedFromCashedCasts)
1279-
&& $this->getEventDispatcher()->hasListeners('eloquent.updated: ' . $this::class)
1280-
) {
1281-
$this->tmpDirtyIfAttributesAreSyncedFromCashedCasts = null;
1282-
}
1281+
$this->tmpDirtyIfAttributesAreSyncedFromCashedCasts = null;
1282+
$this->tmpOriginalBeforeAfterEvents = $this->attributes;
12831283

12841284
$this->fireModelEvent('updated', false);
12851285
}
@@ -1374,6 +1374,8 @@ protected function performInsert(Builder $query)
13741374
$query->insert($attributes);
13751375
}
13761376

1377+
$this->tmpOriginalBeforeAfterEvents = $this->attributes;
1378+
13771379
// We will go ahead and set the exists property to true, so that it is set when
13781380
// the created event is fired, just in case the developer tries to update it
13791381
// during the event. This will allow them to do so and run an update here.

0 commit comments

Comments
 (0)