Migrations
Overview
Sutomo uses a custom migration runner (database/migrations/0000_runner_migration.php) instead of Laravel's default flat migration directory. Migrations are organized into versioned subdirectories (e.g., v1_0_0/, v1_1_0/) to keep the migration history manageable and provide clear versioning.
Version Structure
database/migrations/
├── 0000_runner_migration.php # Custom runner (reads env config)
├── v1_0_0/ # Initial schema
│ ├── 000_migrate_core.php
│ ├── 001_migrate_platform.php
│ ├── 002_migrate_recruitment.php
│ └── ... up to 017_*
├── v1_1_0/ # Next feature batch
│ ├── 002_create_application_management_table.php
│ └── ...
├── v1_2_0/
├── v1_3_0/
├── v1_4_0/
└── v1_5_0/Naming Convention
{version}/
└── {sequence}_{descriptive_snake_name}.php| Part | Rule | Example |
|---|---|---|
{version} | v{major}_{minor}_{patch} | v1_3_0 |
{sequence} | 3-digit zero-padded number | 000, 001, 017 |
{name} | Snake_case description | create_application_management_table |
Sequence Rules
- Start at
000for each version directory - Increment by 1 for each new migration:
000,001,002, ... - Never use
999— it breaks ordering and was previously renamed to017 - Custom ordering overrides exist in the runner for
v1_0_0if needed
How to Create a Migration
1. Determine the version
Check the latest version folder. If there is none for the current feature batch, create a new one:
bash
# If latest is v1_5_0 and you're adding a new feature:
mkdir database/migrations/v1_6_02. Find the next sequence number
bash
ls database/migrations/v1_5_0/
# 000_add_lat_lng_to_schools_table.php
# 001_add_username_to_users_table.php
# ... next is 0073. Generate the migration
Use php artisan make:migration and then move it:
bash
php artisan make:migration add_phone_to_users_table
# Creates: database/migrations/2026_07_30_123456_add_phone_to_users_table.php
mv database/migrations/2026_07_30_123456_add_phone_to_users_table.php \
database/migrations/v1_5_0/007_add_phone_to_users_table.php4. (Optional) Register in runner
If your migration needs a custom execution order within its version folder, add it to sortMigrations() in the runner:
php
private function sortMigrations(string $folder, array $files): array
{
$overrides = match ($folder) {
'v1_0_0' => [
'007_migrate_geographic.php' => 1,
'003_migrate_academic.php' => 2,
// ...
],
default => [],
};
// ...
}Runner Configuration
The runner reads environment variable MIGRATION_RUNNER_FOLDERS:
env
# Default (all versions)
MIGRATION_RUNNER_FOLDERS=v1_0_0,v1_1_0,v1_2_0,v1_3_0,v1_4_0,v1_5_0
# Tests override (phpunit.xml)
<env name="MIGRATION_RUNNER_FOLDERS" value="v1_0_0"/>Rules:
- Folders are executed in the order they appear in the comma-separated list
- Missing folders are silently skipped
- The runner outputs folder names during migration:
--- Migrating: v1_0_0 ---
Migration Rules
Do ✅
- Create a new version directory for each significant feature batch
- Use descriptive names that explain what the migration does
- Keep migrations small and focused — one table or one column change per file
- Use
Schema::hasTable()checks in later versions to avoid duplicates - Test both
up()anddown()methods
Don't ❌
- Don't edit migrations that have already been deployed — create a new one
- Don't use sequence numbers like
999— they break ordering - Don't put unrelated schema changes in the same file
- Don't delete old migrations from production — only from local/dev
- Don't create migrations outside a version directory
Duplicate Table Prevention
When adding a migration in a later version that might overlap with an earlier version, always check:
php
if (! Schema::hasTable('academic_years')) {
Schema::create('academic_years', function (Blueprint $table) {
// ...
});
}Key Files
| File | Purpose |
|---|---|
database/migrations/0000_runner_migration.php | Custom runner — reads folders env, executes migrations in order |
database/migrations/v1_0_0/ to v1_5_0/ | Versioned migration directories |
phpunit.xml | MIGRATION_RUNNER_FOLDERS env for test environment |
.env | MIGRATION_RUNNER_FOLDERS env for local dev |