Migration Operations

Migration operations are source-defined CLI workflows built around MigrationRunner and the configured migrations history table. The important assessment boundary is ordering: discovery precedes history comparison, forward application precedes history insertion, and rollback direction precedes history deletion.

The command files declare the migrate, migrate:rollback, migrate:refresh, and migrate:status handlers. CommandRunner scans Commands/ and registers only instantiable BaseCommand subclasses keyed by their declared name; the handler bodies alone do not prove deployment reachability.

Source-defined flow

flowchart TD
    cli["Database CLI handler"] --> latest["MigrationRunner::latest"]
    cli --> regress["MigrationRunner::regress"]

    latest --> ensure1["ensureTable"]
    ensure1 --> find["findMigrations"]
    find --> found{"Migrations found?"}
    found -- "no" --> latestDone["return true"]
    found -- "yes" --> history["getHistory(group)"]
    history --> up["migrate('up')"]
    up --> skipped{"groupSkip?"}
    skipped -- "no" --> add["addHistory"]
    skipped -- "yes" --> next["continue"]
    up -- "false" --> compensate["regress(-1)"]

    regress --> ensure2["ensureTable"]
    ensure2 --> batches["getBatches"]
    batches --> target["resolve and validate target batch"]
    target --> discoverAll["set namespace null; findMigrations()"]
    discoverAll --> collect["pop batches; getBatchHistory(batch, 'desc')"]
    collect --> gap{"Each history UID found?"}
    gap -- "no" --> gapError["Migrations.gap; stop"]
    gap -- "yes" --> rollbackList["complete rollback migration list"]
    rollbackList --> down["MigrationRunner::migrate('down', migration)"]
    down --> downResult{"wrapper returns true?"}
    downResult -- "yes" --> remove["removeHistory"]
    downResult -- "no" --> rollbackFault["general fault; stop"]

This diagram represents source order and call relationships, not an observed migration run or confirmed database state.

Command comparison

Operation Source-defined behavior Visible output
migrate — imspr/system/Commands/Database/Migrate.php Migrate reads namespace and group options, optionally selects all namespaces or a specified namespace, then calls latest with the selected group. Runner messages are printed. A false runner result adds a general-fault message, after which the handler still writes Done.
migrate:rollback — imspr/system/Commands/Database/MigrateRollback.php MigrateRollback selects a group, resolves -b or the CLI option, and otherwise uses $runner->getLastBatch() - 1 before calling regress. A false runner result adds a general-fault message, then runner messages are printed and the handler writes Done.
migrate:refresh — imspr/system/Commands/Database/MigrateRefresh.php MigrateRefresh calls migrate:rollback with ['-b' => 0], then calls migrate. The visible nested calls do not explicitly pass the handler’s $params. CommandRunner registers only instantiable BaseCommand subclasses. The inspected Migrate and MigrateRollback classes are plain classes, so dispatch and output for these nested calls are not established and may take the command-not-found path.
migrate:status — imspr/system/Commands/Database/MigrateStatus.php MigrateStatus iterates configured PSR-4 namespaces, skips CodeIgniter, Config, and Tests\Support, discovers migrations, reads history, and sorts migration entries by UID. Matching history produces a timestamp; otherwise the entry shows ---. An empty discovery result reports Migrations.noneFound.

Rules that determine migration selection

  • Configuration and history table: imspr/app/Config/Migrations.php declares migrations enabled with public $enabled = true; and names migrations as the history table with public $table = 'migrations';. ensureTable contains the table-creation call createTable($this->table, true) when its creation path is reached; the source does not prove that the table was created in a deployment.
  • File qualification: findNamespaceMigrations lists files below /Database/Migrations/ and passes candidates to migrationFromFile. The latter rejects non-.php paths and filenames that fail the migration regular expression.
  • UID and ordering: migrationFromFile derives a UID from the numeric portion of the version followed by the migration class name. findMigrations collects returned objects by UID and applies ksort($migrations) before forward processing.
  • Forward history comparison: latest discovers migrations, returns true when discovery is empty, and otherwise removes entries whose UID is already present in the selected history. A new batch is based on the last recorded batch plus one.
  • Group filtering: During an up operation, migrate marks a migration as skipped when its database group differs from the configured group filter. latest clears that skip state and continues without treating the skipped migration as a normal forward application or recording it through addHistory.
  • History recording: When the forward path returns true and is not group-skipped, addHistory inserts a row into the configured history table. getHistory queries that table using group criteria and orders rows by id.
  • Rollback target selection: regress obtains batches in ascending order. A negative target is converted to a position in that batch list; an empty history with target 0 returns true, while a nonzero target absent from the list is rejected.
  • Rollback ordering: After target normalization and validation, regress sets the namespace to null and discovers all migrations. It then pops batches and obtains each batch’s history in descending id order, checking each history UID against the discovered collection; a missing UID emits the gap diagnostic before any direction call. Only after this collection and gap-check phase does a separate loop invoke MigrationRunner::migrate('down', $migration) for each migration. That wrapper invokes the migration’s down method and returns true after that call without testing the method’s return value; regress then calls removeHistory when the wrapper result is true.

[!CAUTION]

  • Done is trailing handler output, not a source-proven success signal. Both migrate and migrate:rollback can write it after their runner call returns false.
  • migrate:refresh is destructive at the source level: it requests rollback to batch 0 before invoking migrate. The source does not show parameter forwarding or a result check between those calls.
  • Rollback batch discovery and batch-history selection use batch without the group predicate used by getHistory(). The inspected source therefore does not establish rollback isolation by group.
  • Forward failure requests regress(-1) as compensation, but the source does not establish that compensation completes. Missing classes, missing direction methods, migration exceptions, gaps, and database-operation exceptions can prevent the intended recovery path.
  • No inspected source records an actual migration run or establishes the current schema, applied history, or transaction guarantees.

Updated