Database Migrations
Introductionβ
Migrations are a way to version your database, which often evolves alongside changes to your application.
Migrations are stored in the migrations folder.
Adding a migrationβ
To add a migration, use php bow with the add:migration command followed by the name of the migration (e.g. create_todos_table). Bow will create a file of the same name prefixed with a creation date.
php bow add:migration create_todos_table
The --table and --create options can also be used to specify the table name and indicate whether the migration will create a new table:
# Create a new table
php bow add:migration create_todos_table --create=todos
# Modify an existing table
php bow add:migration add_status_to_todos_table --table=todos
Migration structureβ
A migration class contains two methods: up and rollback. The up method is used to add new tables, columns, or indexes to your database, while the rollback method should reverse the operations performed by the up method.
use Bow\Database\Migration\Migration;
use Bow\Database\Migration\Table;
class Version20190929153939CreateTodosTable extends Migration
{
/**
* Run the migration
*/
public function up(): void
{
$this->create("todos", function (Table $table) {
$table->addIncrement('id');
$table->addString('title');
$table->addInteger('status', ['default' => 1]);
$table->addTimestamps();
});
}
/**
* Reverse the migration
*/
public function rollback(): void
{
$this->dropIfExists("todos");
}
}
Running migrationsβ
To run all your pending migrations:
php bow migration:migrate
# Shortcut
php bow migrate
Rolling back migrationsβ
To roll back the last migration operation:
php bow migration:rollback
To roll back all migrations:
php bow migration:reset
Table managementβ
Creating a tableβ
$this->create("users", function (Table $table) {
$table->addIncrement('id');
$table->addString('name');
$table->addString('email', ['unique' => true]);
$table->addTimestamps();
});
Creating a table if it does not existβ
$this->createIfNotExists("users", function (Table $table) {
$table->addIncrement('id');
$table->addString('name');
$table->addTimestamps();
});
Modifying a tableβ
$this->alter('users', function (Table $table) {
$table->addString('phone', ['nullable' => true]);
});
Renaming a tableβ
$this->renameTable('old_table', 'new_table');
// Rename if it exists
$this->renameTableIfExists('old_table', 'new_table');
Dropping a tableβ
$this->drop('users');
// Drop if it exists
$this->dropIfExists('users');
Custom connectionβ
$this->connection('mysql_secondary')->create("logs", function (Table $table) {
$table->addIncrement('id');
$table->addText('message');
$table->addTimestamps();
});
Table optionsβ
$this->create("users", function (Table $table) {
$table->withEngine('InnoDB');
$table->withCharset('utf8mb4');
$table->withCollation('utf8mb4_unicode_ci');
$table->addIncrement('id');
$table->addString('name');
});
Raw SQLβ
// Run raw SQL inside the migration
$this->sql('ALTER TABLE users ADD INDEX idx_email (email)');
// Alternative
$this->addSql('CREATE INDEX idx_name ON users (name)');
Column typesβ
Numeric columnsβ
| Method | Description |
|---|---|
addIncrement('id') | Auto-incremented INTEGER (primary key) |
addBigIncrement('id') | Auto-incremented BIGINT (primary key) |
addMediumIncrement('id') | Auto-incremented MEDIUMINT (primary key) |
addSmallIntegerIncrement('id') | Auto-incremented SMALLINT (primary key) |
addInteger('column', $attr) | INTEGER |
addIntegerPrimary('column') | INTEGER (primary key) |
addBigInteger('column', $attr) | BIGINT |
addMediumInteger('column', $attr) | MEDIUMINT |
addSmallInteger('column', $attr) | SMALLINT |
addTinyInteger('column', $attr) | TINYINT |
addFloat('column', $attr) | FLOAT |
addFloatPrimary('column') | FLOAT (primary key) |
addDouble('column', $attr) | DOUBLE |
addDoublePrimary('column') | DOUBLE (primary key) |
addBoolean('column') | BOOLEAN |
Text columnsβ
| Method | Description |
|---|---|
addString('column', $attr) | VARCHAR (255 by default) |
addChar('column', $attr) | CHAR |
addText('column', $attr) | TEXT |
addLongtext('column', $attr) | LONGTEXT |
addJson('column', $attr) | JSON |
Date/time columnsβ
| Method | Description |
|---|---|
addDatetime('column', $attr) | DATETIME |
addDate('column', $attr) | DATE |
addTime('column', $attr) | TIME |
addTimestamp('column', $attr) | TIMESTAMP |
addYear('column', $attr) | YEAR |
addTimestamps() | Adds created_at and updated_at |
addSoftDelete() | Adds deleted_at for soft delete |
Special columnsβ
| Method | Description |
|---|---|
addUuid('column', $attr) | UUID |
addUuidPrimary('column', $attr) | UUID (primary key) |
addBinary('column', $attr) | BINARY |
addBlob('column', $attr) | BLOB |
addTinyBlob('column', $attr) | TINYBLOB |
addMediumBlob('column', $attr) | MEDIUMBLOB |
addLongBlob('column', $attr) | LONGBLOB |
addIpAddress('column', $attr) | IP address |
addMacAddress('column', $attr) | MAC address |
addEnum('column', $attr) | ENUM |
addCheck('column', $attr) | CHECK constraint |
Column attributesβ
All add* methods accept an array of attributes:
$table->addString('email', [
'size' => 100, // Column size
'nullable' => true, // Can be NULL
'default' => 'N/A', // Default value
'unique' => true, // UNIQUE constraint
'index' => true, // Add an INDEX
'unsigned' => true, // Unsigned (for numbers)
'primary' => true, // Primary key
'increment' => true, // Auto-increment
]);
The addColumn methodβ
All helper methods use addColumn internally:
$table->addColumn('price', 'decimal', [
'size' => '10,2',
'unsigned' => true,
'default' => 0.00
]);
Raw SQL in a columnβ
$table->addRaw('FULLTEXT INDEX idx_content (title, body)');
Modifying columnsβ
Renaming a columnβ
$this->alter('users', function (Table $table) {
$table->renameColumn('name', 'full_name');
});
Changing a column's typeβ
Each add* method in the table above has a change* counterpart that modifies
the type or attributes of an existing column (changeString, changeInteger,
changeFloat, changeBoolean, changeEnum, changeJson, etc.):
$this->alter('users', function (Table $table) {
$table->changeString('name', ['size' => 500]);
$table->changeInteger('status', ['default' => 0]);
});
For cases not covered by the helpers, the generic
changeColumn($name, $type, $attr) method is available.
Dropping a columnβ
$this->alter('users', function (Table $table) {
$table->dropColumn('temporary_field');
});
Constraintsβ
Foreign keysβ
$this->create("posts", function (Table $table) {
$table->addIncrement('id');
$table->addInteger('user_id', ['unsigned' => true]);
$table->addString('title');
$table->addTimestamps();
// Define the foreign key
$table->addForeign('user_id', [
'references' => 'id',
'on' => 'users',
'onDelete' => 'CASCADE',
'onUpdate' => 'CASCADE'
]);
});
Indexesβ
$this->alter('users', function (Table $table) {
$table->addIndex('email');
});
UNIQUE constraintβ
$this->alter('users', function (Table $table) {
$table->addUnique('email');
});
Dropping a constraint (rollback)β
To undo constraints β useful in the rollback() method:
$this->alter('posts', function (Table $table) {
$table->dropForeign('user_id'); // or an array of columns
$table->dropIndex('idx_title');
$table->dropUnique('email');
$table->dropPrimary();
});
| Method | Description |
|---|---|
dropForeign($name) | Drops a foreign key (string or array) |
dropIndex($name) | Drops an index |
dropUnique($name) | Drops a UNIQUE constraint |
dropPrimary() | Drops the primary key |
Complete exampleβ
use Bow\Database\Migration\Migration;
use Bow\Database\Migration\Table;
class Version20240101120000CreateBlogTables extends Migration
{
public function up(): void
{
// Categories table
$this->create("categories", function (Table $table) {
$table->addIncrement('id');
$table->addString('name');
$table->addString('slug', ['unique' => true]);
$table->addTimestamps();
});
// Posts table
$this->create("posts", function (Table $table) {
$table->withEngine('InnoDB');
$table->withCharset('utf8mb4');
$table->addIncrement('id');
$table->addInteger('category_id', ['unsigned' => true]);
$table->addInteger('author_id', ['unsigned' => true]);
$table->addString('title');
$table->addString('slug', ['unique' => true]);
$table->addText('content');
$table->addEnum('status', ['size' => ['draft', 'published', 'archived']]);
$table->addBoolean('featured', ['default' => false]);
$table->addTimestamps();
$table->addSoftDelete();
$table->addForeign('category_id', [
'references' => 'id',
'on' => 'categories',
'onDelete' => 'CASCADE'
]);
$table->addForeign('author_id', [
'references' => 'id',
'on' => 'users',
'onDelete' => 'CASCADE'
]);
$table->addIndex('status');
});
}
public function rollback(): void
{
$this->dropIfExists("posts");
$this->dropIfExists("categories");
}
}
Is something missing?
If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.