Docs›Arquitetura›Anatomia do MadComponent
Arquitetura

Anatomia do MadComponent

Props públicas, hooks, view(), mount(), boot().

MadComponent (Mad\Component\MadComponent) é a classe base de todo componente reativo do framework — formulários (DRAWER/MODAL), listagens (MadDataGrid extends MadComponent), dashboards, páginas internas (INTERNAL), e até este portal de documentação (MadSitePage extends MadComponent). Entender a anatomia dela é entender a unidade fundamental de UI reativa do MAD.

Estrutura mínima

namespace App\Components\Admin;

use Mad\Component\MadComponent;
use Mad\Form\MadForm;
use Mad\Http\MadResponse;

class PedidoForm extends MadComponent
{
    // Como o framework embrulha o componente quando ele e' aberto
    protected static string $wrapper = self::DRAWER;   // INTERNAL | MODAL | DRAWER
    protected static string $title   = 'Pedido';
    protected static string $size    = 'lg';            // sm | md | lg | xl | CSS direto
    protected static string $side    = 'right';          // right | left (so' DRAWER)

    // Props publicas = state reativo (serializado + criptografado a cada response)
    public int      $pedidoId = 0;
    public MadForm  $form;

    public function mount(int $pedidoId = 0): void
    {
        $this->pedidoId = $pedidoId;
        $this->form     = new MadForm('form');
        // carregado so' na 1a carga (GET) — nao roda de novo em actions AJAX
    }

    public function onSave(): MadResponse
    {
        $pedido = Pedido::updateOrCreate(['id' => $this->pedidoId], $this->form->fields);
        return MadToast::success('Pedido salvo!');
    }

    protected function view(): string|array
    {
        return 'admin.pedido-form';
    }
}

Quatro peças compõem qualquer MadComponent: configuração estática do wrapper (como ele é apresentado), props públicas (o state reativo), hooks de lifecycle (mount() e companhia) e o método abstrato view(), que decide qual template Blade renderizar.

Wrapper — como o componente é apresentado

Quatro props estáticas controlam o "chrome" ao redor do componente — INTERNAL (sem chrome, renderiza direto no main), MODAL (centralizado) ou DRAWER (painel lateral). Cobertura completa, com tamanhos e exemplos de cada um, em Wrappers.

Prop estáticaTipoDefaultFunção
$wrapperstringself::MODALself::INTERNAL / self::MODAL / self::DRAWER.
$titlestring''Header do modal/drawer.
$sizestring'lg'sm/md/lg/xl ou CSS direto ("900px", "60vw").
$sidestring'right'Lado do drawer — right/left. Ignorado em MODAL/INTERNAL.

Lifecycle hooks

Ordem real de execução, idêntica entre 1ª carga e ciclo AJAX exceto pelo que dispara antes da action:

Primeira carga (GET, via show()):
    boot() -> mount($params)
           -> [se o request trouxe `method` != 'show': chama esse metodo]
           -> rendering() -> render() -> rendered() -> dehydrate()

Requisicoes AJAX (mad:click, mad:model, mad:submit -> MadComponentHandler::process()):
    boot() -> hydrate() -> updating()/updated() (por prop alterada via mad:model)
           -> a action chamada -> rendering() -> render() -> rendered() -> dehydrate()
public function boot(): void
{
    // Roda em TODA requisicao (1a carga E cada action AJAX).
    // Setup compartilhado: ler config, resolver tenant, abrir algo que toda
    // execucao precisa — nunca operacao que deva rodar so' uma vez.
}

public function mount(MadRequest $req): void
{
    // Roda APENAS na 1a carga (GET). NAO roda de novo em actions subsequentes.
    $this->categoriaId = $req->int('categoria_id');
    $this->produtos    = Produto::where('categoria_id', $this->categoriaId)->get();
}

public function hydrate(): void
{
    // Roda apos restaurar o estado em requisicoes AJAX (nao na 1a carga).
    // Recarregue aqui o que NAO foi serializado no mad_state (relations, etc).
}

public function updating(string $prop, mixed $old, mixed $new): void
{
    // ANTES de uma prop publica mudar via mad:model. Normalize/valide o valor.
}

public function updated(string $prop, mixed $value): void
{
    // APOS a prop mudar. Efeitos colaterais: recalcular total, buscar dados.
    if ($prop === 'quantidade' || $prop === 'valorUnitario') {
        $this->total = $this->quantidade * $this->valorUnitario;
    }
}

public function rendering(): void { /* antes de render() — prepara dados de ultima hora */ }
public function rendered(string $html): void { /* recebe o HTML ja' renderizado */ }
public function dehydrate(): void { /* antes de serializar o estado — limpe props pesadas */ }

public function exception(\Throwable $e, callable $stopPropagation): void
{
    // Capture uma excecao da action atual. Chame $stopPropagation() para
    // tratar o erro localmente em vez de deixar subir como 500/erro generico.
    if ($e instanceof \App\Exceptions\EstoqueInsuficiente) {
        $this->erro = $e->getMessage();
        $stopPropagation();
    }
}
HookQuando
boot()Toda requisição (inicial e AJAX) — primeiro hook a rodar.
mount(...)Apenas na primeira carga. Aceita array $params, MadRequest $request ou nenhum argumento — o framework injeta o tipo certo.
hydrate()Após restaurar o estado em requisições AJAX (não roda na 1ª carga).
updating() / updatingProp()Antes de uma prop pública mudar via mad:model.
updated() / updatedProp()Depois que a prop mudou via mad:model.
rendering()Imediatamente antes de render().
rendered(string $html)Imediatamente depois de render(), recebe o HTML gerado.
dehydrate()Antes de serializar o estado para o mad_state — último hook do ciclo.
exception(Throwable, callable)Captura exceção de uma action. Chame o callback para impedir que ela suba.

Actions

Métodos públicos chamáveis via mad:click, mad:submit ou mad:change. O framework resolve os argumentos automaticamente por Reflection — por nome, por tipo, ou passando o array inteiro:

// Sem argumentos
public function onSave(): MadResponse { /* ... */ }

// Com argumentos — resolvidos por Reflection (nome do parametro OU tipo)
public function onEdit(int $id): void
{
    $this->registroId = $id;
}

// Tipo array => recebe o payload inteiro (forma legada, ainda suportada)
public function onProcessar(array $params): void { /* ... */ }

// Type-hint MadRequest => injetado automaticamente com os dados da action
public function onFiltrar(MadRequest $req): MadResponse
{
    $this->busca = $req->string('busca');
    return new MadResponse();
}

// Array posicional (ex: linha de field-list) => mapeado por posicao
public function onRowEdit(array $rowData, int $editIndex): void { /* ... */ }
Nem todo método público é uma action remota

MadComponentHandler usa uma blacklist, não whitelist: qualquer método público é chamável via mad_action, exceto métodos que começam com _ e os hooks/método de render declarados na própria base MadComponent (render, rendering, rendered, boot, mount, hydrate, dehydrate, updating, updated, exception, show, fill, forceFullRender). Isso bloqueia automaticamente qualquer método novo que a base venha a ganhar no futuro — uma subclasse precisa sobrescrever explicitamente para tornar algo chamável.

Uma exceção explícita: as 4 actions do MadDbBlocksTrait (usadas por <mad-comments> e <mad-attachments>). O trait foi içado para a própria base, e método de trait reporta getDeclaringClass() = a classe que o usa — ou seja MadComponent —, então a regra acima passou a barrar exatamente o caso que o içamento veio resolver (blockAdd respondia "Método não permitido"). Não afrouxa nada: essas actions exigem o mad_state criptografado que o componente emitiu (model, FK e colunas viajam assinados) e passam pelo PermissionGate como qualquer outra.

onShow() — nome reservado, não é hook de lifecycle

MadComponent declara public function onShow(): void na base como um no-op vazio. Ele não é chamado automaticamente por show(), mount() ou pelo ciclo AJAX — existe apenas como ponto de entrada nomeado convencional, para que navigate="ClienteForm::onShow({id})" e o parâmetro method do request resolvam sempre para um método existente, em vez de estourar em classes que não o definem.

Quem chama onShowComo
show()Só se o request trouxer method=onShow — e sempre depois de boot()/mount().
navigate / click-target"ClienteForm::onShow({id})" — MadAction tira os parênteses e monta a URL /app/{slug}/onShow; os args declarados viram query params, nunca segmento de URL.
MadFiltersTraitSobrescreve onShow() para re-renderizar com os filtros aplicados (onFiltrar() só delega para ele) — é o submit dos dash-filters-*.
MadResponse::teleport()Aceita o método a chamar após o mount — teleport('#painel', DocDetail::class, 'onShow', ['id' => 42]).
Para inicializar, use mount() — não onShow()

Carregar dados, ler o request e preencher props é trabalho de mount(): ele roda sempre na primeira carga, recebe os parâmetros já resolvidos e não depende de o chamador ter passado method=onShow. Enquanto a subclasse não sobrescreve onShow(), ele continua declarado em MadComponent e o MadComponentHandler o recusa como action AJAX pela regra da classe declarante — sobrescrever é o que o torna chamável via mad_action.

Variáveis injetadas na view

Toda view de um MadComponent recebe automaticamente:

VariávelTipoDescrição
$thatMadComponentA própria instância — acesse props públicas via $that->prop.
$_component / $__componentMadComponentAlias de $that.
$madhelperHelper de contexto de render (MadRenderContext::madHelper()).
props públicas—Cada prop pública também fica disponível direto pelo nome (flattening de contexto) — MadForm é achatado em seus fields.
<h1>{{ $that->titulo }}</h1>
<mad-input-field name="nome" :value="$that->nome" />
<mad-btn mad:click="onSalvar">Salvar</mad-btn>

Defesa contra mass-assignment em mad:model

Valores que chegam via mad:model passam por _isModelAssignable() antes de tocar qualquer prop. Chaves começadas com _ são sempre recusadas; para props públicas, uma blacklist fixa bloqueia nomes sensíveis mesmo que a subclasse declare uma prop pública com esse nome — password, senha, permissions, role, is_admin, tenant_id, created_by/updated_by/deleted_by, _token/csrf_token, entre outras. Props públicas já inicializadas como objeto (ex.: um MadForm) também não podem ser sobrescritas por um escalar vindo do cliente. Subclasses podem reforçar essa regra sobrescrevendo _isModelAssignable().

Subclasses na base do framework

MadDataGrid
Listagens reativas — busca, sort, filtros, paginação, export.
MadSitePage
Páginas públicas reativas — wire endpoint em /public/_mad-wire, INTERNAL forçado.
MadDashboard
Base para dashboards com filtros declarativos e auto-refresh.
MadFullCalendar / MadKanban / MadGantt
Componentes especializados de calendário/kanban/gantt, todos sobre o mesmo ciclo de vida.

Próximos passos