Migrations
A migration is a pair of schema changes in one class: up() applies it and down() takes it back off.
Kettle scaffolds them, runs them in timestamp order and tracks how far it has got.
Schema Builder covers building the statements themselves.
Writing a Migration#
A migration is a class extending Pop\Db\Sql\Migration\AbstractMigration with an up() and a down().
The adapter is already on it as $this->db, so the body is the schema builder and nothing else:
<?php
declare(strict_types=1);
use Pop\Db\Sql\Migration\AbstractMigration;
class CreateWidgetsTable extends AbstractMigration
{
public function up(): void
{
$schema = $this->db->createSchema();
$schema->create('widgets')
->int('id', 16)->increment()
->varchar('name', 255)->notNullable()
->primary('id');
$schema->execute();
}
public function down(): void
{
$schema = $this->db->createSchema();
$schema->dropIfExists('widgets');
$schema->execute();
}
}
Write down() if you ever intend to roll back — an empty one rolls back cleanly and changes nothing. The
leading timestamp in the filename is what orders migrations against each other.
Nothing about up() restricts you to DDL. $this->db is a full adapter, so a migration that adds a
column and backfills it runs the UPDATE right there — see Querying for the
adapter API and Query Builder for building that statement portably.
Running Migrations with Kettle#
./kettle scaffolds and runs migrations against the connections in app/config/database.php. Each
connection key gets its own directory under database/migrations/, and the optional <database>
argument on every command below picks one — default when you leave it off, or all for every
directory in turn.
./kettle migrate:create CreateWidgetsTable
./kettle migrate:run
./kettle migrate:run all
./kettle migrate:rollback
| Command | Does |
|---|---|
migrate:create <class> [<database>] |
writes a timestamped migration file into database/migrations/<database>/ |
migrate:run [<steps>] [<database>] |
runs the next <steps> pending migrations, one by default; all runs every pending one |
migrate:rollback [<steps>] [<database>] |
calls down() on the last <steps> applied migrations, one by default |
migrate:reset [<database>] |
rolls every applied migration back |
migrate:point [<id>] [<database>] |
moves the recorded position without running anything — a migration's timestamp, or latest |
db:config [<database>] |
prompts for credentials and writes them into app/config/database.php |
db:test [<database>] |
checks that the configured connection can be opened |
migrate:create appends a uniqid() to the class name you give it, so CreateWidgetsTable becomes a
file holding class CreateWidgetsTable68f2a1c9b0e4d. Rename the class and the file together if you
would rather not carry that, or call Pop\Db\Sql\Migrator::create() yourself, which does not add it.
Position is kept in a .current file holding the timestamp of the last migration applied, and a full
rollback deletes it. Write a .table file there instead, naming a Pop\Db\Record class, and the migrator
keeps state in that table — id, file, batch number and timestamp per row. migrate:point rewrites the
position either way.
Running Migrations from Code#
Pop\Db\Sql\Migrator is what the migrate: commands drive, and nothing stops you driving it yourself —
a deploy script, a test bootstrap, or a setup command of your own. It takes the adapter and the directory
holding the migration files:
use Pop\Db\Record;
use Pop\Db\Sql\Migrator;
$migrator = new Migrator(Record::getDb(), __DIR__ . '/../database/migrations/default');
$migrator->run();
run() and rollback() take a number of steps, one by default; runAll() and rollbackAll() cover the
whole directory. Migrator::create($class, $path) writes a new migration file and returns its name,
without the uniqid() suffix migrate:create appends.
Migrations are applied in batches, and the migrator tracks them: getCurrentBatch() is the batch last
applied, getNextBatch() the one the next run will use, and getByBatch($batch) lists the migrations in
a given batch. getCurrent() returns the position itself — the timestamp in the .current file, or the
last row in the state table.
See Also#
- Schema Builder — the
createSchema()API a migration'sup()anddown()are written in - Seeding — the seed classes and
.sqlfiles that sit beside migrations - Querying — the adapter a migration's
$this->dbis, and its transaction API - Records & the ORM — the table classes a migration's schema is written for
- Connecting & Adapters —
app/config/database.phpand the connections Kettle reads from it - pop-db README — the migrator API surface