Migrations
Como criar e rodar migrations no MAD.
Migrations
O MAD usa o sistema de migrations nativo do Laravel — Illuminate\Database\Migrations,
rodado via php artisan. Não existe mais um CLI próprio (php mad migrate...) nem uma
convenção de pasta por conexão: é o Artisan padrão, com uma particularidade documentada
abaixo para lidar com as conexões lógicas do MAD (iam, log, business, comm, ged,
ai).
Onde ficam
Uma pasta única, convenção padrão do Laravel:
database/migrations/
├── 0001_01_01_000000_create_users_table.php ← infra Laravel (sessions, password_reset_tokens)
├── 0001_01_01_000001_create_cache_table.php
├── 0001_01_01_000002_create_jobs_table.php
├── 0001_01_01_000100_create_mad_schema.php ← schema MAD consolidado
└── 2026_06_30_000001_add_active_to_mad_iam_unit.php
Crie uma nova com:
php artisan make:migration create_orders_table
php artisan make:migration add_status_to_orders_table
O Artisan já detecta o padrão do nome (create_X_table, add_X_to_Y_table) e gera o stub
certo (Schema::create(...) ou Schema::table(...)).
Conexões — Schema::connection(), não $connection
A app define 6 conexões lógicas em config/database.php (control-plane iam/log,
data-plane business/comm/ged/ai), todas apontando por padrão para o mesmo arquivo
app/database/mad.sqlite (single-DB). O tracking de migrations (a tabela migrations
que registra o que já rodou) fica na conexão default (DB_CONNECTION, normalmente
sqlite/database/database.sqlite) — uma tabela só, central.
Isso significa: não declare protected $connection = 'iam'; na classe da migration
(isso espalharia o tracking entre tabelas migrations diferentes por conexão). Em vez
disso, chame Schema::connection('iam') explicitamente dentro de up()/down():
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Guarda defensiva: a conexão pode não existir em todo ambiente
// (ex.: instalação que não usa o módulo correspondente).
if (empty(config('database.connections.iam'))) {
return;
}
$schema = Schema::connection('iam');
// Idempotente: hasTable/hasColumn evita erro em reinstalação ou em
// bancos onde a tabela já nasceu com a coluna (CREATE IF NOT EXISTS
// do schema consolidado).
if (! $schema->hasTable('mad_iam_unit') || $schema->hasColumn('mad_iam_unit', 'active')) {
return;
}
$schema->table('mad_iam_unit', function (Blueprint $table) {
$table->char('active', 1)->default('Y');
});
}
public function down(): void
{
if (empty(config('database.connections.iam'))) {
return;
}
$schema = Schema::connection('iam');
if ($schema->hasTable('mad_iam_unit') && $schema->hasColumn('mad_iam_unit', 'active')) {
$schema->table('mad_iam_unit', function (Blueprint $table) {
$table->dropColumn('active');
});
}
}
};
Esse é o padrão real usado em
database/migrations/2026_06_30_000001_add_active_to_mad_iam_unit.phpdeste projeto — copie a estrutura (guard de config + guard de schema) para qualquer migration que altere uma tabela MAD existente.
Para uma tabela nova, sem necessidade de guard de coluna:
public function up(): void
{
Schema::connection('business')->create('orders', function (Blueprint $table) {
$table->id();
$table->string('status')->default('pending');
$table->decimal('total', 10, 2)->default(0);
$table->foreignId('customer_id');
$table->timestamps();
$table->index('status');
});
}
public function down(): void
{
Schema::connection('business')->dropIfExists('orders');
}
Comandos Artisan
# Rodar todas as migrations pendentes
php artisan migrate
# Ver status (rodadas vs pendentes)
php artisan migrate:status
# Rollback do último batch
php artisan migrate:rollback
# Rollback de N batches
php artisan migrate:rollback --step=3
# Reverter tudo e rodar de novo
php artisan migrate:fresh
# Criar tabela do repositório de migrations (raro precisar manualmente)
php artisan migrate:install
Tipos de coluna disponíveis (Blueprint)
// Inteiros
$table->id(); // BIGINT unsigned auto increment (PK)
$table->foreignId('customer_id'); // BIGINT unsigned (pra FK)
$table->integer('qty');
$table->unsignedBigInteger('user_id');
$table->tinyInteger('status');
// Strings
$table->string('name'); // VARCHAR(255)
$table->string('code', 50); // VARCHAR(50)
$table->text('description');
$table->longText('content');
$table->char('uf', 2);
// Números
$table->decimal('price', 10, 2);
$table->float('weight');
$table->double('latitude', 10, 7);
// Datas
$table->date('birth_date');
$table->time('start_time');
$table->dateTime('scheduled_at');
$table->timestamp('confirmed_at')->nullable();
$table->timestamps(); // created_at + updated_at
$table->softDeletes(); // deleted_at
// Booleano
$table->boolean('active')->default(true);
// JSON
$table->json('metadata')->nullable();
// UUID / ULID
$table->uuid('uuid')->unique();
$table->ulid('ulid')->unique();
// Modificadores
->nullable()
->default('valor')
->after('coluna')
->unsigned()
->unique()
->index()
->comment('texto')
Operações de schema fora de migrations
Para checks/operações ad-hoc no código da aplicação (fora do fluxo de migration),
use a facade Schema nativa do Laravel, sempre qualificando a conexão:
use Illuminate\Support\Facades\Schema;
Schema::connection('business')->hasTable('orders'); // bool
Schema::connection('business')->hasColumn('orders', 'status'); // bool
Schema::connection('business')->getColumnListing('orders'); // array de colunas
Schema::connection('business')->table('orders', function ($table) {
$table->string('tracking_code')->nullable()->after('status');
});
Para ler o catálogo do banco (lista de tabelas, colunas, FKs, índices, contagem de linhas) em vez de só checar existência, veja
Mad\Database\SchemaIntrospector— documentado em Introspecção de schema. Desde a 5.54.0 ele não faz maisCOUNT(*)por tabela nem uma query de catálogo por tabela.
Tabelas de fila e notificação
jobs, failed_jobs e notifications (conexão iam) já vêm definidas na migration
consolidada database/migrations/0001_01_01_000100_create_mad_schema.php — não exigem
migration própria nem comando separado. php artisan migrate cobre tudo, framework e
app, numa passada só.
Há um diretório
packages/mad-framework/src/mad/database/migrations/no pacote, mas ele não é carregado pelo Laravel nesta versão (semloadMigrationsFrom()registrado) — trate-o como vestigial; a fonte real dessas tabelas é o schema consolidado acima.
Bancos suportados
driver em config/database.php |
Engine |
|---|---|
sqlite |
SQLite (default — app/database/mad.sqlite) |
mysql / mariadb |
MySQL / MariaDB |
pgsql |
PostgreSQL |
sqlsrv |
SQL Server (conexão Laravel avulsa — não é opção de DB_MAD_DRIVER) |
A engine usada pelas 6 conexões lógicas do MAD é controlada pela env DB_MAD_DRIVER, que
aceita sqlite (default), mysql, mariadb ou pgsql — qualquer outro valor não é
tratado. Com sqlite, todas as conexões apontam para app/database/mad.sqlite (WAL,
busy_timeout 5000, transações DEFERRED). Com engine de servidor, todas apontam por
padrão para um único banco compartilhado (o sidecar expõe um database só).
Cada conexão pode sobrescrever o alvo individualmente — DB_<CONN>_HOST, _PORT,
_DATABASE, _USERNAME, _PASSWORD (ex.: DB_IAM_DATABASE) — e só cai no
DB_MAD_* compartilhado quando o par específico não está no .env. É assim que o
provisionamento por tenant aponta cada empresa para o seu próprio banco/host sem tocar no
código: ver Estratégias de isolamento.