Database tables and migrations
Tables
Describe each table's current shape by extending Table:
use Bignall\WPFoundation\Database\Table;
final class BookingsTable extends Table {
public function name(): string {
return 'acme_bookings'; // The site prefix is added for you: wp_acme_bookings, wp_2_acme_bookings, ...
}
public function columns(): string {
return "id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
starts_at datetime NOT NULL,
status varchar(20) NOT NULL DEFAULT 'confirmed',
PRIMARY KEY (id),
KEY starts_at (starts_at)";
}
}
Plugin::create( __FILE__ )->tables( new BookingsTable() );
On activation and upgrade, dbDelta() creates missing tables and adds missing columns and
indexes. On uninstall (with data deletion enabled), tables are dropped. On multisite, a deleted
site's tables are dropped along with it.
dbDelta is strict about formatting:
- One column or key per line.
- Use
KEY, notINDEX. - Two spaces after
PRIMARY KEY. - Lowercase types, e.g.
bigint(20),varchar(191).
dbDelta never drops or renames columns and doesn't reliably change column types. Use a migration for those.
Table also provides tableName(), tableNameForSite( $id ), exists(), install() and drop().
Migrations
A migration is a one-time change tied to the version that introduced it:
->migration( '1.2.0', function ( Plugin $plugin ): void {
global $wpdb;
$table = ( new BookingsTable() )->tableName();
$wpdb->query( "ALTER TABLE {$table} CHANGE `when` starts_at datetime NOT NULL" );
} )
Or as a class implementing Bignall\WPFoundation\Database\Migration (version() and up()),
registered with ->migrations( new RenameWhenColumn(), new BackfillStatus() ).
How they run
- Only on upgrade: migrations newer than the stored version, and no newer than the current version, run in version order. Migrations sharing a version run in the order you added them.
- Not on a fresh install, because tables are created from their current schema.
- Before tables are synced with dbDelta. A migration sees the previous schema, so if it needs a new column, it should add that column itself. dbDelta then finds the column already there. This order also lets renames work: rename the column in a migration, and dbDelta won't add an empty copy under the new name.
- After each version's migrations finish, the stored version advances. If a migration throws, the next request resumes from there.
Make migrations safe to re-run where you can (check whether a column exists before adding it, for example). An interrupted request can repeat the version that was in progress.