{"record":{"id":"8df08b286593e38a","repo":"yiisoft/yii2","slug":"the-object-being-deleted-is-outdated","errorCode":null,"errorMessage":"The object being deleted is outdated.","messagePattern":"The object being deleted is outdated\\.","errorType":"exception","errorClass":"yii\\db\\StaleObjectException","httpStatus":null,"severity":"error","filePath":"framework/db/ActiveRecord.php","lineNumber":789,"sourceCode":"     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.\n     * @throws StaleObjectException\n     */\n    protected function deleteInternal()\n    {\n        if (!$this->beforeDelete()) {\n            return false;\n        }\n\n        // we do not check the return value of deleteAll() because it's possible\n        // the record is already deleted in the database and thus the method will return 0\n        $condition = $this->getOldPrimaryKey(true);\n        $lock = $this->optimisticLock();\n        if ($lock !== null) {\n            $condition[$lock] = $this->$lock;\n        }\n        $result = static::deleteAll($condition);\n        if ($lock !== null && !$result) {\n            throw new StaleObjectException('The object being deleted is outdated.');\n        }\n        $this->setOldAttributes(null);\n        $this->afterDelete();\n\n        return $result;\n    }\n\n    /**\n     * Returns a value indicating whether the given active record is the same as the current one.\n     * The comparison is made by comparing the table names and the primary key values of the two active records.\n     * If one of the records [[isNewRecord|is new]] they are also considered not equal.\n     * @param ActiveRecord $record record to compare to\n     * @return bool whether the two active records refer to the same row in the same database table.\n     */\n    public function equals($record)\n    {\n        if ($this->isNewRecord || $record->isNewRecord) {\n            return false;","sourceCodeStart":771,"sourceCodeEnd":807,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/db/ActiveRecord.php#L771-L807","documentation":"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.","triggerScenarios":"$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.","commonSituations":"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.","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"],"exampleFix":"// before\n$model->delete(); // may throw StaleObjectException\n\n// after\nuse yii\\db\\StaleObjectException;\n\ntry {\n    $model->delete();\n} catch (StaleObjectException $e) {\n    $model->refresh();\n    // row changed or vanished: decide explicitly\n    Yii::$app->session->setFlash('warning', 'Record was modified by another user.');\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"use yii\\db\\StaleObjectException;\n\ntry {\n    $model->delete();\n} catch (StaleObjectException $e) {\n    $model->refresh();\n    if ($model->getOldPrimaryKey(false) !== null) {\n        // version changed but row still exists: let the user confirm\n        Yii::$app->session->setFlash('warning', 'Record changed by another user. Please retry.');\n    }\n    // row gone: treat as deleted\n}","preventionTips":["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"],"tags":["optimistic-locking","concurrency","delete","stale-object","yii2"],"backgroundTag":"optimistic-lock-conflict","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}