Migrations
Migrations create and change your database tables from code, so every copy of the site, on your computer or on the server, has the same structure.
Connecting
Migrations use the database in .env. See database for the settings. php flo db:check tells you whether the database is configured and reachable, and php flo db:show lists its tables with row counts.
Creating a migration
php flo make:migration create_posts_table
creates database/migrations/<timestamp>_create_posts_table.php. A name like create_posts_table gives you a CREATE TABLE template for posts; --create=posts does the same for any name, and --table=posts gives an ALTER TABLE template for changing an existing table. The file name, without .php, is the migration's ID, and the timestamp keeps files in order.
A migration returns an object with up(), which makes the change, and down(), which undoes it:
<?php
declare(strict_types=1);
use FloCMS\CLI\Database\Migration;
use FloCMS\Core\Database;
return new class extends Migration
{
public function up(Database $db): void
{
$db->query('CREATE TABLE IF NOT EXISTS `posts` (
' . $this->id($db) . ',
`title` VARCHAR(190) NOT NULL,
`created_at` DATETIME NULL
)' . $this->tableOptions($db));
}
public function down(Database $db): void
{
$db->query('DROP TABLE IF EXISTS `posts`');
}
};
Inside a migration:
$dbis the sameDatabaseclass your models use, soquery()and the query builder both work.$this->id($db)writes an auto-increment primary key, and$this->tableOptions($db)adds InnoDB andutf8mb4on MySQL.up()anddown()run in a transaction. MySQL commitsCREATE,ALTERandDROPimmediately anyway; setpublic bool $transactional = false;when a migration must not run in one.- A migration without
down()can't be rolled back;migrate:rollbackstops with an error.
Running migrations
php flo migrate # run pending migrations
php flo migrate:status # what ran, what's pending, changed or missing
php flo migrate:rollback # undo the last batch
php flo migrate:fresh --seed # drop all tables, migrate again, then seed
- Migrations run in file-name order, in numbered batches. Each one is recorded in the
migrationstable with a checksum, somigrate:statusnotices edited or missing files. - Only one
migrateruns at a time. - With
APP_ENV=production, commands that change data ask first. Deployment scripts pass--force. --pretendlists the migrations that would run, without executing them; it does not print SQL.--seedruns the seeders after a normal migration run, not a pending-migration preview.migrate:rollback --step=3undoes the last three migrations instead of the last batch.
Caution
migrate:fresh drops every table in the database. It asks first, and refuses on production without --force.
Warning
Never edit a migration that has already run on another copy of the site. migrate:status reports it as changed there, but the change is never applied. Create a new migration for the next change instead.
Migrations from packages
The users table is a migration in your database/migrations/. Packages ship their own migrations: flocms-api's create the API tables. php flo migrate runs them straight from vendor/, so on a new site one command sets up everything. They're tracked per package:
php flo migrate:status --package=hostkurd/flocms-api
php flo migrate --package=hostkurd/flocms-api
A package registers its migration folders in its composer.json. See writing commands.
Core's module migration format also works: a file that returns ['up' => [...SQL...], 'down' => [...]].
Accepting that array format does not make php flo migrate discover module manifests. The core module runner reads the manifest's explicit migration files and uses its own state and ledger tables. See module migrations.
Seeders
Starting data, such as categories or a demo account, is added by seeders. See seeding.