Docs›Banco de dados›Migrations
Banco de dados

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.php deste 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 mais COUNT(*) 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 (sem loadMigrationsFrom() 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.