{"record":{"id":"f353acf1d5a2609e","repo":"yiisoft/yii2","slug":"the-object-being-deleted-is-outdated-f353ac","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/BaseActiveRecord.php","lineNumber":917,"sourceCode":"     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.\n     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data\n     * being deleted is outdated.\n     * @throws Exception in case delete failed.\n     */\n    public function delete()\n    {\n        $result = false;\n        if ($this->beforeDelete()) {\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->_oldAttributes = null;\n            $this->afterDelete();\n        }\n\n        return $result;\n    }\n\n    /**\n     * Returns a value indicating whether the current record is new.\n     * @return bool whether the record is new and should be inserted when calling [[save()]].\n     */\n    public function getIsNewRecord()\n    {\n        return $this->_oldAttributes === null;\n    }\n\n    /**","sourceCodeStart":899,"sourceCodeEnd":935,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/db/BaseActiveRecord.php#L899-L935","documentation":"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.","triggerScenarios":"$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.","commonSituations":"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.","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"],"exampleFix":"// before\n$model->delete(); // unhandled StaleObjectException on conflict\n\n// after\nuse yii\\db\\StaleObjectException;\n\ntry {\n    $model->delete();\n} catch (StaleObjectException $e) {\n    $model->refresh();\n    Yii::$app->session->setFlash('error', 'This record was just changed by someone else. Review and delete again.');\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->getPrimaryKey() === null) {\n        return; // row already gone: treat as success\n    }\n    Yii::$app->session->setFlash('warning', 'Record was modified by another user. Please review and retry.');\n}","preventionTips":["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"],"tags":["optimistic-locking","concurrency","delete","stale-object","conflict"],"backgroundTag":"optimistic-lock-conflict","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}