phalcon/cphalcon · error · Phalcon\Db\Exceptions\NestedTransactionChangeBlocked

Nested transaction with savepoints behavior cannot be change

Error message

Nested transaction with savepoints behavior cannot be changed while a transaction is open

What it means

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.

Source

Thrown at phalcon/Db/Adapter/AbstractAdapter.zep:1180

    {
        let this->eventsManager = eventsManager;
    }

    /**
     * Sets the dialect used to produce the SQL
     */
    public function setDialect(<DialectInterface> dialect)
    {
        let this->dialect = dialect;
    }

    /**
     * Set if nested transactions should use savepoints
     */
    public function setNestedTransactionsWithSavepoints(bool nestedTransactionsWithSavepoints) -> <AdapterInterface>
    {
        if unlikely this->transactionLevel > 0 {
            throw new NestedTransactionChangeBlocked();
        }

        if unlikely !this->dialect->supportsSavePoints() {
            throw new SavepointsNotSupported();
        }

        let this->transactionsWithSavepoints = nestedTransactionsWithSavepoints;

        return this;
    }

    /**
     * Enables/disables options in the Database component.
     *
     * The flags are stored as process-global `Phalcon\Support\Settings`
     * (`db.escape_identifiers`, `db.force_casting`) and therefore affect every
     * connection in the process at once, last-writer-wins. Call this once at
     * bootstrap; it is not per-connection configuration. Because the

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Configure the flag immediately after constructing the adapter, before any begin()
  2. If configuration is lazy, guard it: if ($connection->getTransactionLevel() === 0) { $connection->setNestedTransactionsWithSavepoints(true); }
  3. Move adapter configuration out of request-time code into a dedicated bootstrap step

Example fix

// before
$connection->begin();
$connection->setNestedTransactionsWithSavepoints(true); // throws

// after
$connection->setNestedTransactionsWithSavepoints(true); // once, at bootstrap
$connection->begin();
Defensive patterns

Strategy: validation

Validate before calling

if ($connection->getTransactionLevel() === 0) {
    $connection->setNestedTransactionsWithSavepoints($enable);
} else {
    // defer until the current unit of work closes
    $deferredFlags[] = $enable;
}

Try / catch

use Phalcon\Db\Exceptions\NestedTransactionChangeBlocked;

try {
    $connection->setNestedTransactionsWithSavepoints($enable);
} catch (NestedTransactionChangeBlocked $e) {
    $logger->warning('Savepoint mode change deferred until transaction closes');
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/d584390be624e2d6. Report an issue: GitHub.