yiisoft/yii2 · error · yii\db\StaleObjectException
The object being deleted is outdated.
Error message
The object being deleted is outdated.
What it means
BaseActiveRecord::deleteInternal() applies optimistic locking on delete: when optimisticLock() names a column, its in-memory value joins the DELETE WHERE condition built from getOldPrimaryKey(true). If deleteAll() returns 0 rows, the row's version changed since the model was loaded and StaleObjectException signals the conflict before afterDelete() runs.
Source
Thrown at framework/db/BaseActiveRecord.php:917
* Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being deleted is outdated.
* @throws Exception in case delete failed.
*/
public function delete()
{
$result = false;
if ($this->beforeDelete()) {
// we do not check the return value of deleteAll() because it's possible
// the record is already deleted in the database and thus the method will return 0
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
$result = static::deleteAll($condition);
if ($lock !== null && !$result) {
throw new StaleObjectException('The object being deleted is outdated.');
}
$this->_oldAttributes = null;
$this->afterDelete();
}
return $result;
}
/**
* Returns a value indicating whether the current record is new.
* @return bool whether the record is new and should be inserted when calling [[save()]].
*/
public function getIsNewRecord()
{
return $this->_oldAttributes === null;
}
/**View on GitHub (pinned to 66f00d18a2)
Solutions
- Catch StaleObjectException around delete() and either refresh() + retry once or report a conflict (409)
- Make the UI re-fetch the record immediately before offering delete
- Confirm optimisticLock() names the right column and all writers maintain it
- Disable optimisticLock() for this model if best-effort deletes are acceptable
Example fix
// before
$model->delete(); // unhandled StaleObjectException on conflict
// after
use yii\db\StaleObjectException;
try {
$model->delete();
} catch (StaleObjectException $e) {
$model->refresh();
Yii::$app->session->setFlash('error', 'This record was just changed by someone else. Review and delete again.');
} Defensive patterns
Strategy: try-catch
Try / catch
use yii\db\StaleObjectException;
try {
$model->delete();
} catch (StaleObjectException $e) {
$model->refresh();
if ($model->getPrimaryKey() === null) {
return; // row already gone: treat as success
}
Yii::$app->session->setFlash('warning', 'Record was modified by another user. Please review and retry.');
} Prevention
- Catch StaleObjectException on delete() wherever optimisticLock() is active
- Re-fetch the record immediately before delete confirmations
- Ensure delete flows return a distinguishable 'conflict' result to the UI
When it happens
Trigger: $model->delete() where the version column changed after load (another request saved or deleted the row); deleting via a model instance held open in a long workflow; version drift from updateAll() bypassing the increment.
Common situations: Double-submit of a delete button; concurrent admin edits; background jobs deleting the same records as users; stale model instances kept in session or cache.
Related errors
- The object being deleted is outdated.
- The object being updated is outdated.
- {class} does not have a primary key. You should either defin
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/f353acf1d5a2609e.
Report an issue: GitHub.