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

  1. Catch StaleObjectException around delete() and either refresh() + retry once or report a conflict (409)
  2. Make the UI re-fetch the record immediately before offering delete
  3. Confirm optimisticLock() names the right column and all writers maintain it
  4. 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

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


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/f353acf1d5a2609e. Report an issue: GitHub.