yiisoft/yii2 · error · yii\db\StaleObjectException
The object being deleted is outdated.
Error message
The object being deleted is outdated.
What it means
yii\db\ActiveRecord::deleteInternal() implements optimistic locking: when optimisticLock() returns a column name, that column's in-memory value is added to the DELETE's WHERE clause. If deleteAll() then affects 0 rows, the stored lock value no longer matches the loaded model — another request modified or removed the row in between — and StaleObjectException is thrown.
Source
Thrown at framework/db/ActiveRecord.php:789
* Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
* @throws StaleObjectException
*/
protected function deleteInternal()
{
if (!$this->beforeDelete()) {
return false;
}
// 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->setOldAttributes(null);
$this->afterDelete();
return $result;
}
/**
* Returns a value indicating whether the given active record is the same as the current one.
* The comparison is made by comparing the table names and the primary key values of the two active records.
* If one of the records [[isNewRecord|is new]] they are also considered not equal.
* @param ActiveRecord $record record to compare to
* @return bool whether the two active records refer to the same row in the same database table.
*/
public function equals($record)
{
if ($this->isNewRecord || $record->isNewRecord) {
return false;View on GitHub (pinned to 66f00d18a2)
Solutions
- Wrap delete() in try/catch (StaleObjectException), refresh() the model, then either retry the delete or report the conflict to the user
- Verify optimisticLock() returns the intended version column and that every write path increments it
- Remove or narrow the optimisticLock() override if the table genuinely does not need versioned deletes
- Reload the model immediately before deleting instead of reusing a long-held instance
Example fix
// before
$model->delete(); // may throw StaleObjectException
// after
use yii\db\StaleObjectException;
try {
$model->delete();
} catch (StaleObjectException $e) {
$model->refresh();
// row changed or vanished: decide explicitly
Yii::$app->session->setFlash('warning', 'Record was modified by another user.');
} Defensive patterns
Strategy: try-catch
Try / catch
use yii\db\StaleObjectException;
try {
$model->delete();
} catch (StaleObjectException $e) {
$model->refresh();
if ($model->getOldPrimaryKey(false) !== null) {
// version changed but row still exists: let the user confirm
Yii::$app->session->setFlash('warning', 'Record changed by another user. Please retry.');
}
// row gone: treat as deleted
} Prevention
- Catch StaleObjectException wherever delete() touches optimistic-locked models
- Reload the model right before delete in user-facing flows
- Never bypass the version increment with updateAll() on locked tables
When it happens
Trigger: $model->delete() where the record's optimistic-lock version changed after the model was loaded (concurrent save in another tab or request); deleting a row another process already deleted; a prior updateAll() changed the version column without incrementing it.
Common situations: Two users editing/deleting the same record; long-lived edit forms; API clients retrying with stale payloads; version column drift caused by batch updates that bypass optimistic locking.
Related errors
- The object being deleted is outdated.
- The object being updated is outdated.
- Unknown scenario: $scenario
- Invalid validation rule: a rule must specify both attribute
- The directory does not exist: $path
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/8df08b286593e38a.
Report an issue: GitHub.