Migration Class
The Migration Class is a way to manage changes to your database schema over time. It provides a structured and versioned way to track and apply changes (such as creating tables, adding columns, or modifying indexes) in code.
This allows you to evolve the database schema in a controlled and reproducible manner.
Enabling and Loading Migration
Like most other classes in LavaLust, the Migration library is initialized in your controller using the $this->call->library() method:
<?php
$this->call->library('migration');
Once loaded, the Migration library object will be available using:
<?php
$this->migration;
You must also enable migrations in the configuration file:
<?php
// File: app/config/migration.php
$config['migration_enabled'] = true;
$config['migration_path'] = 'app/migrations';
$config['migration_table'] = 'migrations';
Controller Example
Create a controller (e.g. MigrationController.php) to handle migration commands:
<?php
class MigrationController extends Controller {
public function __construct()
{
parent::__construct();
$this->call->library('migration');
}
// ==========================================================
// CREATE
// ==========================================================
/**
* Create a new migration file.
*
* Route: create-migration/(:any)
* create-migration/(:any)/(:any) (with type)
*
* @param string $migration_class
* @param string|null $type create|modify|drop
*/
public function create_migration($migration_class, $type = 'create')
{
$this->migration->create_migration($migration_class, $type);
}
// ==========================================================
// MIGRATE
// ==========================================================
/**
* Run all pending migrations.
*
* Route: migrate
*/
public function migrate()
{
$this->migration->migrate(false);
}
public function migrate_dry()
{
$this->migration->migrate(true);
}
/**
* Migrate up to a specific version.
*
* Route: migrate-to/(:num)
*
* @param int $version
*/
public function migrate_to($version)
{
$this->migration->migrate_to($version);
}
// ==========================================================
// ROLLBACK
// ==========================================================
/**
* Rollback the most recent batch (or N batches).
*
* Route: rollback
* rollback/(:num)
*
* @param int $steps Number of batches to rollback (default: 1)
*/
public function rollback($steps = 1)
{
$this->migration->rollback($steps);
}
/**
* Rollback down to a specific version.
*
* Route: rollback-to/(:num)
*
* @param int $version
*/
public function rollback_to($version)
{
$this->migration->rollback_to($version);
}
/**
* Rollback every applied migration.
*
* Route: rollback-all
*/
public function rollback_all()
{
$this->migration->rollback_all();
}
// ==========================================================
// REFRESH / RESET / FRESH
// ==========================================================
/**
* Rollback all + re-run all migrations.
*
* Route: refresh
*/
public function refresh()
{
$this->migration->refresh();
}
/**
* Rollback all migrations WITHOUT re-running them.
*
* Route: reset
*/
public function reset()
{
$this->migration->reset();
}
/**
* Drop ALL tables and re-run all migrations.
*
* Route: fresh
*/
public function fresh()
{
$this->migration->fresh();
}
// ==========================================================
// INSPECTION
// ==========================================================
/**
* Show the migration status table.
*
* Route: status
*/
public function status()
{
$this->migration->status();
}
/**
* Preview UP code for pending migrations.
*
* Route: preview
*/
public function preview()
{
$this->migration->preview();
}
}
Routes Example
Add routes so you can call these actions via the browser:
<?php
// -------------------------------------------------------------------
// CREATE
// -------------------------------------------------------------------
$router->get('create-migration/{migration_class}', 'MigrationController::create_migration');
$router->get('create-migration/{migration_class}/{type}', 'MigrationController::create_migration');
// -------------------------------------------------------------------
// MIGRATE
// -------------------------------------------------------------------
$router->get('migrate/dry-run', 'MigrationController::migrate_dry');
$router->get('migrate', 'MigrationController::migrate');
$router->get('migrate-to/{version}', 'MigrationController::migrate_to');
// -------------------------------------------------------------------
// ROLLBACK
// -------------------------------------------------------------------
$router->get('rollback', 'MigrationController::rollback');
$router->get('rollback/{steps}', 'MigrationController::rollback');
$router->get('rollback-to/{version}', 'MigrationController::rollback_to');
$router->get('rollback-all', 'MigrationController::rollback_all');
// -------------------------------------------------------------------
// REFRESH / RESET / FRESH
// -------------------------------------------------------------------
$router->get('refresh', 'MigrationController::refresh');
$router->get('reset', 'MigrationController::reset');
$router->get('fresh', 'MigrationController::fresh');
// -------------------------------------------------------------------
// INSPECTION
// -------------------------------------------------------------------
$router->get('status', 'MigrationController::status');
$router->get('preview', 'MigrationController::preview');
Creating a Migration File
Visit the following URL to create a new migration file:
run command php public/index.php create-migration/create_users_table
This will generate a file like app/migrations/001_create_users_table.php.
You can also create you own php lava command by adding this class in app/commands or using command php lava make:command Migration
<?php
/**
* Command: Migration
*
* Auto-discovered by the LavaLust CLI.
* Proxies migration actions to the framework's route-based migration system.
*/
class Migration
{
public static $command = 'migration';
public static $description = 'Run database migrations';
public static $arguments = [
'[action]' => 'Action: run (default), create-migration, rollback, rollback-all, rollback-to, ' .
'migrate-to, refresh, reset, fresh, status, preview',
'[name]' => 'Migration name (for create-migration) OR target version (for migrate-to / rollback-to) ' .
'OR number of batches (for rollback, default: 1)',
];
/**
* Environment variables read by the framework's Migration class.
* Passing options this way means no route changes are needed.
*/
const ENV_DRY_RUN = 'LAVA_MIGRATION_DRY_RUN';
const ENV_FORCE = 'LAVA_MIGRATION_FORCE';
/**
* Map CLI actions to framework routes.
*/
protected static $route_map = [
'run' => 'migrate',
'create-migration' => 'create-migration',
'rollback' => 'rollback',
'rollback-all' => 'rollback-all',
'rollback-to' => 'rollback-to',
'migrate-to' => 'migrate-to',
'refresh' => 'refresh',
'reset' => 'reset',
'fresh' => 'fresh',
'status' => 'status',
'preview' => 'preview',
];
/**
* Actions that require a version number argument.
*/
protected static $requires_version = [
'migrate-to',
'rollback-to',
];
/**
* Actions that honor --dry-run.
*/
protected static $supports_dry_run = [
'run',
'migrate-to',
];
/**
* Valid migration template types.
*/
protected static $valid_types = ['create', 'modify', 'drop'];
/**
* @param string|null $action Action to run (default: run)
* @param array $flags Flags such as --dry-run, --force, --type=modify
* @param string|null $name Migration name, version, or step count
* @param string|null $extra Optional positional migration type (create-migration)
*/
public function handle($action = null, array $flags = [], $name = null, $extra = null)
{
$action = $action ?? 'run';
// cli.php only treats `--x` as a flag, so `-h` arrives as a positional
// (either as the action or as the name).
if ($action === '-h' || $action === 'help') {
$this->print_help(null);
return;
}
if ($this->has_flag($flags, 'help') || $name === '-h') {
$this->print_help($action);
return;
}
if (!isset(static::$route_map[$action])) {
echo danger("Unknown migration action: \"{$action}\"") . PHP_EOL;
echo "Available actions: " . implode(', ', array_keys(static::$route_map)) . PHP_EOL;
echo "Run `php lava migration --help` for usage details." . PHP_EOL;
exit(1);
}
$dry_run = $this->has_flag($flags, 'dry-run');
// Never print "dry run" for an action that would really execute
if ($dry_run && !in_array($action, static::$supports_dry_run, true)) {
echo danger("--dry-run is only supported for: " . implode(', ', static::$supports_dry_run) . ".") . PHP_EOL;
exit(1);
}
$route = $this->build_route($action, $flags, $name, $extra);
if ($route === null) {
exit(1);
}
$index = PUBLIC_DIR . 'index.php';
if (!file_exists($index)) {
echo danger("index.php not found at: {$index}") . PHP_EOL;
exit(1);
}
// Options are handed to the child process through its environment
// (reset first so nothing leaks in from the parent shell).
putenv(self::ENV_DRY_RUN);
putenv(self::ENV_FORCE);
if ($dry_run) {
putenv(self::ENV_DRY_RUN . '=1');
echo $this->info("Dry run enabled — no changes will be made.") . PHP_EOL;
}
if ($action === 'fresh' && $this->has_flag($flags, 'force')) {
putenv(self::ENV_FORCE . '=1');
}
$php = defined('PHP_BINARY') && PHP_BINARY !== '' ? PHP_BINARY : 'php';
$command = sprintf('%s %s %s', escapeshellarg($php), escapeshellarg($index), escapeshellarg($route));
$exit_code = 0;
passthru($command, $exit_code);
// Propagate the child's exit status so scripts/CI can detect failures
exit((int) $exit_code);
}
/**
* Build the framework route string from CLI arguments.
*
* @param string $action
* @param array $flags
* @param string|null $name
* @param string|null $extra
* @return string|null Route string, or null on validation failure
*/
protected function build_route($action, array $flags, $name, $extra = null)
{
// create-migration requires a name
if ($action === 'create-migration') {
if (!$name) {
echo danger("Migration name is required.") . PHP_EOL;
echo "Example: php lava migration create-migration create_user_table" . PHP_EOL;
echo " php lava migration create-migration add_status_to_posts_table modify" . PHP_EOL;
return null;
}
// The name becomes a URL segment, so keep it route-safe
if (!preg_match('/^[A-Za-z][A-Za-z0-9_\-]*$/', (string) $name)) {
echo danger("Invalid migration name \"{$name}\". Use letters, numbers and underscores.") . PHP_EOL;
return null;
}
$type = $this->extract_type($flags, $extra);
if ($type === false) {
return null;
}
$route = 'create-migration/' . $name;
if ($type !== null) {
$route .= '/' . $type;
}
return $route;
}
// migrate-to / rollback-to require a version
if (in_array($action, static::$requires_version, true)) {
if ($name === null || !ctype_digit((string) $name)) {
echo danger("A numeric version is required for \"{$action}\".") . PHP_EOL;
echo "Example: php lava migration {$action} 5" . PHP_EOL;
return null;
}
return static::$route_map[$action] . '/' . (int) $name;
}
// rollback accepts an optional step count (default 1)
if ($action === 'rollback') {
$steps = 1;
if ($name !== null) {
if (!ctype_digit((string) $name) || (int) $name < 1) {
echo danger("Steps must be a positive number.") . PHP_EOL;
echo "Example: php lava migration rollback 3" . PHP_EOL;
return null;
}
$steps = (int) $name;
}
return static::$route_map[$action] . '/' . $steps;
}
// Static route for everything else
return static::$route_map[$action];
}
/**
* Extract the migration type.
*
* Accepts: --type=modify, --create/--modify/--drop, a positional type
* argument, or a bare type word among the flags.
*
* @param array $flags
* @param string|null $positional
* @return string|null|false Type, null for "use default", false on an invalid value
*/
protected function extract_type(array $flags, $positional = null)
{
// --type=modify (cli.php gives $flags['type'] = 'modify')
if (array_key_exists('type', $flags)) {
$type = $flags['type'];
if (is_string($type) && in_array($type, static::$valid_types, true)) {
return $type;
}
echo danger("Invalid migration type. Allowed: " . implode(', ', static::$valid_types) . ".") . PHP_EOL;
return false;
}
// Shorthand flags: --create, --modify, --drop
foreach (static::$valid_types as $type) {
if ($this->has_flag($flags, $type)) {
return $type;
}
}
// Optional third positional (needs cli.php to pass it, see notes)
if ($positional !== null && in_array($positional, static::$valid_types, true)) {
return $positional;
}
return null;
}
/**
* Check for a flag. cli.php parses `--dry-run` into $flags['dry-run'] = true
* and `--type=x` into $flags['type'] = 'x'. A raw "--name" entry is also
* accepted in case the flags ever arrive as a plain list.
*
* @param array $flags
* @param string $name Flag name without the leading dashes
* @return bool
*/
protected function has_flag(array $flags, $name)
{
if (array_key_exists($name, $flags) && $flags[$name] !== false) {
return true;
}
return in_array('--' . $name, $flags, true);
}
/**
* Local output helpers: cli.php only defines danger() and success(),
* so info() and bold() are provided here instead of relying on globals.
*/
protected function info($string)
{
return "\e[0;36m" . $string . "\e[0m";
}
protected function bold($string)
{
return "\e[1m" . $string . "\e[0m";
}
/**
* Print help text for the migration command.
*
* @param string|null $action Specific action to describe, or null for all
*/
protected function print_help($action = null)
{
if ($action && $action !== 'run' && isset(static::$route_map[$action])) {
$this->print_action_help($action);
return;
}
echo $this->bold("Migration Command") . PHP_EOL;
echo str_repeat("=", 70) . PHP_EOL;
echo "Usage: php lava migration [action] [options]" . PHP_EOL . PHP_EOL;
echo "Available actions:" . PHP_EOL;
echo " run Run all pending migrations (default)" . PHP_EOL;
echo " migrate-to <version> Run migrations up to a specific version" . PHP_EOL;
echo " create-migration <name> [type] Create a new migration file" . PHP_EOL;
echo " rollback [steps] Rollback N batches (default: 1)" . PHP_EOL;
echo " rollback-to <version> Rollback down to a specific version" . PHP_EOL;
echo " rollback-all Rollback all applied migrations" . PHP_EOL;
echo " refresh Rollback all + re-run all migrations" . PHP_EOL;
echo " reset Rollback all (without re-running)" . PHP_EOL;
echo " fresh Drop ALL tables and re-run migrations" . PHP_EOL;
echo " status Show migration status table" . PHP_EOL;
echo " preview Show pending migration UP code" . PHP_EOL . PHP_EOL;
echo "Options:" . PHP_EOL;
echo " --help, -h Show this help or action-specific help" . PHP_EOL;
echo " --dry-run Show what would run, change nothing (run, migrate-to)" . PHP_EOL;
echo " --force Skip the confirmation prompt (fresh)" . PHP_EOL;
echo " --type=create|modify|drop Migration template type" . PHP_EOL;
echo " --create, --modify, --drop Shorthand for --type" . PHP_EOL . PHP_EOL;
echo "Examples:" . PHP_EOL;
echo " php lava migration" . PHP_EOL;
echo " php lava migration run" . PHP_EOL;
echo " php lava migration run --dry-run" . PHP_EOL;
echo " php lava migration create-migration create_users_table" . PHP_EOL;
echo " php lava migration create-migration add_status_to_posts_table --type=modify" . PHP_EOL;
echo " php lava migration rollback 3" . PHP_EOL;
echo " php lava migration rollback-to 5" . PHP_EOL;
echo " php lava migration migrate-to 8 --dry-run" . PHP_EOL;
echo " php lava migration status" . PHP_EOL;
echo " php lava migration fresh" . PHP_EOL;
}
/**
* Print help text for a specific action.
*
* @param string $action
*/
protected function print_action_help($action)
{
echo $this->bold("Migration: {$action}") . PHP_EOL;
echo str_repeat("-", 50) . PHP_EOL;
switch ($action) {
case 'run':
echo "Run all pending migrations in ascending version order." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration run [--dry-run]" . PHP_EOL;
echo "Aliases: php lava migration (no args)" . PHP_EOL;
break;
case 'migrate-to':
echo "Run migrations up to (and including) the given version." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration migrate-to <version> [--dry-run]" . PHP_EOL;
echo "Example: php lava migration migrate-to 5" . PHP_EOL;
break;
case 'create-migration':
echo "Create a new numbered migration file." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration create-migration <name> [--type=TYPE]" . PHP_EOL;
echo "Types: create (default), modify, drop" . PHP_EOL . PHP_EOL;
echo "Examples:" . PHP_EOL;
echo " php lava migration create-migration create_users_table" . PHP_EOL;
echo " php lava migration create-migration add_email_to_users_table modify" . PHP_EOL;
echo " php lava migration create-migration drop_old_logs_table drop" . PHP_EOL;
break;
case 'rollback':
echo "Rollback the most recent migration batch (or N batches)." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration rollback [steps]" . PHP_EOL;
echo "Example: php lava migration rollback 3" . PHP_EOL;
break;
case 'rollback-to':
echo "Rollback all migrations newer than the given version." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration rollback-to <version>" . PHP_EOL;
echo "Example: php lava migration rollback-to 3" . PHP_EOL;
break;
case 'rollback-all':
echo "Rollback every applied migration in reverse version order." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration rollback-all" . PHP_EOL;
break;
case 'refresh':
echo "Rollback all migrations, then re-run them." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration refresh" . PHP_EOL;
break;
case 'reset':
echo "Rollback all migrations WITHOUT re-running them." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration reset" . PHP_EOL;
break;
case 'fresh':
echo "Drop ALL database tables and re-run all migrations." . PHP_EOL;
echo danger("WARNING: This destroys all data!") . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration fresh [--force]" . PHP_EOL;
echo " --force skips the 'yes' confirmation prompt" . PHP_EOL;
break;
case 'status':
echo "Display a table of all migrations and their applied/pending state." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration status" . PHP_EOL;
break;
case 'preview':
echo "Show the up() method body of all pending migrations." . PHP_EOL . PHP_EOL;
echo "Usage: php lava migration preview" . PHP_EOL;
break;
default:
echo "No help available for this action." . PHP_EOL;
}
}
}
Open the file and define your schema:
<?php
class Create_users_table {
private $_lava;
protected $dbforge;
public function __construct()
{
$this->_lava =& lava_instance();
$this->_lava->call->dbforge();
}
public function up()
{
// Define the table structure
$this->_lava->dbforge->add_field([
'id' => [
'type' => 'INT',
'constraint' => 11,
'auto_increment' => TRUE
]
]);
$this->_lava->dbforge->add_key('id', TRUE);
$this->_lava->dbforge->create_table('your_table_name');
}
public function down()
{
// Drop the table
$this->_lava->dbforge->drop_table('your_table_name');
}
}
Running Migrations
Run all pending migrations
Run
php lava migration run
Rollback the last migration batch
Run:
php lava migration rollback 1
Available Methods Reference
Method |
Description |
|---|---|
|
Creates a new migration file with the given name inside |
|
Executes all available migration files that have not been run yet |
|
Executes all available migration files up to (and including) the given version |
|
Reverts (drops) the most recent migration batch |
|
Reverts (drops) all migration batches newer than the given version |
|
Reverts (drops) all migration batch |
|
Refresh migration batch |
|
Reverts (drops) all migration batch without re-running them |
|
Drop ALL database tables and re-run all migrations |
|
Show status of migration batch |
|
Show the up() method body of all pending migrations |
Note
Each migration file must contain an up() method (to apply changes)
and a down() method (to rollback changes).