Short answer
Phalcon does not provide a built-in pre-flight schema-drift check before down(), and there is no widely documented target-version command that automatically unwinds all later migrations to a chosen version. The migration runner executes the DDL inside down() against the live connection without confirming that referenced tables, columns, or indexes still match the migration's assumptions. To make rollback safer, add a manual guard at the top of down() that inspects the live schema and throws before any destructive DDL runs.
Confirmed behavior and assumptions
- Phalcon DevTools migrations are generally run per file or as pending batches; version tracking and available APIs depend on your Phalcon version and migration runner.
Phalcon\Db\Adapter\Pdo\AbstractAdapter exposes getTables(), describeColumns(), describeIndexes(), and describeReferences(), which support a manual pre-flight assertion.
- Transactional DDL is database-dependent. PostgreSQL generally supports transactional DDL, while MySQL and MariaDB often auto-commit DDL, so a transaction around
down() may not undo a partially applied rollback.
Assumption: You are using Phalcon DevTools with Phalcon 4 or 5 and a relational adapter such as MySQL, PostgreSQL, or SQLite. If you use a custom runner, the guard logic still applies, but the applied-migrations table name may differ.
Steps for this case
- Open the migration file whose
down() you want to protect.
- At the top of
down(), add a guard that checks each object the method touches. Example for dropping a column:
public function down()
{
$connection = $this->getConnection();
$table = 'users';
$column = 'deleted_at';
$tables = $connection->getTables();
if (!in_array($table, $tables, true)) {
throw new \RuntimeException("Rollback aborted: table {$table} does not exist.");
}
$columns = $connection->describeColumns($table);
$columnNames = array_map(fn($col) => $col->getName(), $columns);
if (!in_array($column, $columnNames, true)) {
throw new \RuntimeException("Rollback aborted: column {$column} is missing from {$table}. Schema drift detected.");
}
$connection->dropColumn($table, $column);
}
- Repeat the guard for indexes, foreign keys, or other objects referenced in
down() using describeIndexes() and describeReferences().
- If your engine supports transactional DDL (for example, PostgreSQL), wrap the DDL in
$connection->begin() and $connection->commit() inside a try/catch block that calls $connection->rollback() on failure. For MySQL or MariaDB, treat DDL as non-transactional and rely on the pre-flight guard instead.
- Test the migration on a restored backup after introducing drift, such as adding an extra column, to confirm the guard throws before any
DROP or ALTER executes.
Verification
- Confirm your Phalcon version and migration runner, and check for an applied-migrations table such as
schema_migrations.
- Run
describeColumns() against your target table in a standalone script to confirm the returned structure matches your guard's expectations.
- Verify your database's DDL autocommit behavior before trusting a transaction around
down().
Missing diagnostic detail
Which database engine are you using? This changes whether a transaction around down() can fully undo a partially applied rollback.