{"record":{"id":"d584390be624e2d6","repo":"phalcon/cphalcon","slug":"nested-transaction-with-savepoints-behavior-cannot","errorCode":null,"errorMessage":"Nested transaction with savepoints behavior cannot be changed while a transaction is open","messagePattern":"Nested transaction with savepoints behavior cannot be changed while a transaction is open","errorType":"exception","errorClass":"Phalcon\\Db\\Exceptions\\NestedTransactionChangeBlocked","httpStatus":null,"severity":"error","filePath":"phalcon/Db/Adapter/AbstractAdapter.zep","lineNumber":1180,"sourceCode":"    {\n        let this->eventsManager = eventsManager;\n    }\n\n    /**\n     * Sets the dialect used to produce the SQL\n     */\n    public function setDialect(<DialectInterface> dialect)\n    {\n        let this->dialect = dialect;\n    }\n\n    /**\n     * Set if nested transactions should use savepoints\n     */\n    public function setNestedTransactionsWithSavepoints(bool nestedTransactionsWithSavepoints) -> <AdapterInterface>\n    {\n        if unlikely this->transactionLevel > 0 {\n            throw new NestedTransactionChangeBlocked();\n        }\n\n        if unlikely !this->dialect->supportsSavePoints() {\n            throw new SavepointsNotSupported();\n        }\n\n        let this->transactionsWithSavepoints = nestedTransactionsWithSavepoints;\n\n        return this;\n    }\n\n    /**\n     * Enables/disables options in the Database component.\n     *\n     * The flags are stored as process-global `Phalcon\\Support\\Settings`\n     * (`db.escape_identifiers`, `db.force_casting`) and therefore affect every\n     * connection in the process at once, last-writer-wins. Call this once at\n     * bootstrap; it is not per-connection configuration. Because the","sourceCodeStart":1162,"sourceCodeEnd":1198,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Db/Adapter/AbstractAdapter.zep#L1162-L1198","documentation":"setNestedTransactionsWithSavepoints() may only be called when the adapter's transactionLevel is 0. Switching savepoint behavior while a transaction is open would corrupt the adapter's nesting bookkeeping (which levels were started with or without savepoints), so NestedTransactionChangeBlocked is thrown. The flag is a setup-time setting, not a runtime toggle.","triggerScenarios":"Calling $connection->setNestedTransactionsWithSavepoints(true) after begin() and before commit()/rollback(); lazy bootstrapping code (DI factory, event listener, model initializer) that configures the adapter on first use, which happens to run inside an open transaction.","commonSituations":"Adapter setup placed in a shared factory that also serves transactional requests; middleware or ORM event hooks that toggle savepoint mode; deferred/lazy connection services configured at first query — inside someone else's transaction.","solutions":["Configure the flag immediately after constructing the adapter, before any begin()","If configuration is lazy, guard it: if ($connection->getTransactionLevel() === 0) { $connection->setNestedTransactionsWithSavepoints(true); }","Move adapter configuration out of request-time code into a dedicated bootstrap step"],"exampleFix":"// before\n$connection->begin();\n$connection->setNestedTransactionsWithSavepoints(true); // throws\n\n// after\n$connection->setNestedTransactionsWithSavepoints(true); // once, at bootstrap\n$connection->begin();","handlingStrategy":"validation","validationCode":"if ($connection->getTransactionLevel() === 0) {\n    $connection->setNestedTransactionsWithSavepoints($enable);\n} else {\n    // defer until the current unit of work closes\n    $deferredFlags[] = $enable;\n}","typeGuard":null,"tryCatchPattern":"use Phalcon\\Db\\Exceptions\\NestedTransactionChangeBlocked;\n\ntry {\n    $connection->setNestedTransactionsWithSavepoints($enable);\n} catch (NestedTransactionChangeBlocked $e) {\n    $logger->warning('Savepoint mode change deferred until transaction closes');\n}","preventionTips":["Configure setNestedTransactionsWithSavepoints() once, immediately after adapter construction","Keep adapter configuration out of request handlers, event listeners, and lazy initializers that can run inside transactions","If configuration is dynamic, always gate it on getTransactionLevel() === 0"],"tags":["php","phalcon","db","transaction","savepoint","configuration","lifecycle"],"backgroundTag":"nested-transaction-misuse","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}