Docs›Componentes (Admin)›mad-file-field
Componentes (Admin)

mad-file-field

Upload único (storage disk|db, name-column).

Componentes de upload de arquivos com drag-and-drop. Substituem <input type="file"> e <mad-input-field type="file">.

IMPORTANTE: Sempre usar form->save($record) ao inves de fillRecord + $record->save() manual — o MadForm::save() encapsula tudo: preenche campos, salva single-file, persiste ($record->save()), e roda hooks pos-persistencia (multi-file, etc).


<mad-file-field> — Upload de arquivo unico

Props

Prop Tipo Default Descricao
name string '' Nome do campo (obrigatorio)
label string '' Label do campo
accept string '' Extensoes aceitas: .pdf,.doc,.docx
max-size string '' Tamanho maximo: 5MB
storage string '' disk (salva no filesystem) ou db (salva como BLOB base64 no banco)
folder string 'uploads' Diretorio destino (somente com storage="disk")
name-column string '' Coluna para armazenar o nome original do arquivo
file-name string 'prefix' Modo do nome: prefix, unique, original, record (ver secao abaixo)
hint string '' Texto de ajuda
error string '' Mensagem de erro
required bool false Campo obrigatorio
disabled bool false Campo desabilitado
attrs string '' Atributos HTML extras
width string '' Largura do campo (CSS, ex: 300px)
max-width string '' Largura maxima do campo (CSS)

Nomeacao do arquivo (file-name)

Controla como o nome do arquivo e gerado ao salvar no disco.

{{-- Default: uniqid_contrato.pdf (evita colisao) --}}
<mad-file-field name="arquivo" storage="disk" folder="uploads" />

{{-- Nome 100% gerado: 65a7f8b2c4d3e.pdf (descarta nome original) --}}
<mad-file-field name="foto" storage="disk" folder="uploads/fotos" file-name="unique" />

{{-- Manter nome original: contrato.pdf --}}
<mad-file-field name="doc" storage="disk" folder="uploads/docs" file-name="original" />

{{-- ID do registro + nome: 42_contrato.pdf --}}
<mad-file-field name="doc" storage="disk" folder="uploads/docs" file-name="record" />
Modo Resultado Quando usar
prefix 65a7f8b2c4d3e_contrato.pdf Padrao — evita colisao, preserva nome
unique 65a7f8b2c4d3e.pdf Quando nome original nao importa
original contrato.pdf Quando precisa manter o nome exato
record 42_contrato.pdf Vincula ao registro (fallback uniqid se novo)

Evento change (callback ao selecionar arquivo)

Quando mad:change e definido, ao selecionar (ou arrastar) um arquivo, ele e enviado imediatamente ao servidor. O metodo PHP recebe o path temporario do arquivo e pode ler o conteudo para preencher campos do formulario antes do save.

<mad-file-field name="planilha" label="Planilha"
    accept=".csv,.xlsx" mad:change="onPlanilhaSelect" />
public function onPlanilhaSelect(string $tempPath): void
{
    $rows = array_map('str_getcsv', file($tempPath));
    $this->form->set('total_linhas', count($rows));
    $this->form->set('primeira_coluna', $rows[0][0] ?? '');
}

O metodo recebe a chave relativa do arquivo no scratch — prefixo mad_uploads/ no disco mad_tmp (storage/app/mad-tmp/mad_uploads/...), gerenciado por Mad\Service\MadScratchStorage. Leia sempre via Storage, nao por caminho absoluto:

public function onPlanilhaSelect(string $key): void
{
    $csv  = \Mad\Service\MadScratchStorage::disk()->get($key);
    $rows = array_map('str_getcsv', explode("\n", trim($csv)));
    $this->form->set('total_linhas', count($rows));
}

Funciona com auto-bind (void) ou MadResponse.

API uniforme: mad-file-field, mad-multi-file-field e mad-image-field usam a mesma assinatura — o metodo PHP sempre recebe string $tempPath (single) ou array $tempPaths (multi).

Upload com storage automatico (RECOMENDADO)

Quando storage="disk" e definido, o save() salva o arquivo no disco e preenche as colunas automaticamente. Zero codigo de arquivo no controller.

<mad-file-field name="arquivo" label="Contrato"
    storage="disk" folder="uploads/contratos" name-column="nome_arquivo"
    accept=".pdf,.doc,.docx" max-size="10MB" required />
// Controller — nenhum codigo de arquivo necessario
public function onSave(): MadResponse
{
    $record = DB::connection('business')->transaction(function () {
        $record = $this->registroId ? Contrato::findOrFail($this->registroId) : new Contrato();
        $this->form->save($record);  // fillRecord + save() + afterStore
        return $record;
    });

    return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
}

O save() automaticamente:

  1. fillRecord() detecta o campo file com storage no schema
  2. Salva o arquivo em uploads/contratos/uniqid_nome.pdf
  3. Seta $record->arquivo = 'uploads/contratos/xxx.pdf'
  4. Seta $record->nome_arquivo = 'contrato.pdf' (name-column)
  5. $record->save() persiste no banco (Eloquent)
  6. Se nenhum arquivo foi enviado, mantem o valor existente

Onde o arquivo realmente vai parar (5.x)

Todo file-IO do framework passa pela API de Filesystem do Laravel (Storage/Flysystem) — nao existe mais fopen/move_uploaded_file em caminho absoluto. Sao dois discos com papeis distintos:

Disco Classe Root default Papel
mad_uploads Mad\Service\MadUploadStorage storage/app/mad Arquivos persistidos (storage="disk", GED, anexos de chat, modelos de importacao)
mad_tmp Mad\Service\MadScratchStorage storage/app/mad-tmp Scratch transiente: mad_uploads/ (staging do $_FILES), import/, output/ (exports da grid), blob/ (loadBlob)
  • O disco de persistencia vem de config('mad.uploads.disk') (env MAD_UPLOAD_DISK). Vazio ⇒ mad_uploads local. Aponte para s3/MinIO e o codigo e o mesmo: o banco guarda sempre a chave relativa (uploads/contratos/xxx.pdf), nunca caminho absoluto nem URL.
  • MAD_UPLOAD_DISK apontando para disco inexistente em config/filesystems.php lanca excecao na resolucao — falha ruidosa, nao silenciosa. E put()/putContent() lancam RuntimeException quando o driver retorna false, para nunca gravar path no banco sem objeto atras.
  • O scratch e sempre local, mesmo com S3 configurado: e por-request / por-no, mandar staging pro bucket so somaria latencia e lixo remoto.
  • Download e sempre streamado pelo app (rota autenticada/assinada do MadDownloadController) — nunca URL direta do bucket.

Removidos no 5.x: a prop/config outputDir e a classe MadUploaderService. Codigo que dependia delas deve usar MadUploadStorage / MadScratchStorage. Nao ha fallback de leitura para arquivos antigos na raiz do projeto — migrar os paths e manual.

Storage no banco (BLOB base64)

Quando storage="db", o arquivo e salvo como BLOB base64 direto no banco. Suporta MySQL (LONGBLOB), PostgreSQL (BYTEA), SQL Server (VARBINARY), Oracle (BLOB).

<mad-file-field name="conteudo_arquivo" label="Arquivo"
    storage="db" name-column="nome_arquivo" required />

No onEdit, usar loadBlob() para extrair o BLOB para tmp/:

public function onEdit(int $id): void
{
    $record = MeuModel::findOrFail($id);   // leitura nao precisa de transacao
    $this->form->loadBlob($record, 'conteudo_arquivo', 'nome_arquivo');
    $this->form->fill($record);
}

Upload simples (sem storage automatico)

<mad-file-field name="arquivo" label="Anexo" />

Sem storage, o dev acessa $_FILES['arquivo'] manualmente.


<mad-multi-file-field> — Upload de multiplos arquivos

Props

Prop Tipo Default Descricao
name string '' Nome do campo (obrigatorio, usa name[] automaticamente)
label string '' Label do campo
accept string '*' MIME types ou extensoes: image/*,.pdf
max-files int 0 Maximo de arquivos (0 = ilimitado)
max-size int 0 Tamanho maximo por arquivo em KB (0 = ilimitado)
storage string '' disk (filesystem) ou db (BLOB base64 no banco)
folder string 'uploads' Diretorio destino (storage="disk")
mode string 'comma' comma (virgula, so disk) ou table (tabela relacionada)
model string '' Model Eloquent da tabela filha (mode=table)
foreign-key string '' Coluna FK na tabela filha (mode=table)
path-column string '' Coluna do caminho (disk) ou BLOB (db) na tabela filha (mode=table)
name-column string '' Coluna para nome original (mode=table)
file-name string 'prefix' Modo do nome: prefix, unique, original, record
hint string '' Texto de ajuda
error string '' Mensagem de erro
required bool false Campo obrigatorio
disabled bool false Campo desabilitado
attrs string '' Atributos HTML extras
width string '' Largura do campo (CSS, ex: 300px)
max-width string '' Largura maxima do campo (CSS)

Evento change (callback ao adicionar arquivos)

<mad-multi-file-field name="anexos" label="Anexos"
    accept=".pdf,.csv" mad:change="onAnexosAdded" />
public function onAnexosAdded(array $tempPaths): void
{
    foreach ($tempPaths as $path) {
        // Ler cada arquivo...
    }
    $this->form->set('total_anexos', count($tempPaths));
}

Modo virgula — caminhos separados por virgula

<mad-multi-file-field name="anexos" label="Anexos"
    storage="disk" folder="uploads/docs" mode="comma"
    accept=".pdf,.doc" :max-files="5" />

Resultado no banco: anexos = 'uploads/docs/abc.pdf,uploads/docs/xyz.doc'

// Controller — zero codigo de arquivo
DB::connection('business')->transaction(function () {
    $record = MeuModel::findOrFail($this->registroId);
    $this->form->save($record);
});

Modo tabela — um registro por arquivo

<mad-multi-file-field name="arquivos" label="Arquivos"
    storage="disk" folder="uploads/negociacao" mode="table"
    model="NegociacaoArquivo" foreign-key="negociacao_id"
    path-column="conteudo_arquivo" name-column="nome_arquivo"
    accept=".pdf,image/*" :max-files="10" />

O save() cria um registro na tabela filha para cada arquivo:

  • $child->negociacao_id = $record->id
  • $child->conteudo_arquivo = 'uploads/negociacao/xxx.pdf'
  • $child->nome_arquivo = 'contrato.pdf'

Modo tabela com BLOB — salva base64 no banco

<mad-multi-file-field name="arquivos" label="Arquivos"
    storage="db" mode="table"
    model="NegociacaoArquivo" foreign-key="negociacao_id"
    path-column="conteudo_arquivo" name-column="nome_arquivo"
    accept=".pdf,image/*" :max-files="10" />

Cada arquivo e salvo como BLOB base64 na coluna conteudo_arquivo da tabela filha. Suporta MySQL (LONGBLOB), PostgreSQL (BYTEA), SQL Server (VARBINARY), Oracle (BLOB).

Multi-file sem storage (manual)

<mad-multi-file-field name="anexos" label="Anexos" />

Sem storage, o dev itera $_FILES['anexos'] manualmente.


form->save() vs fillRecord+save

Cenario Usar
Formulario padrao (recomendado) $this->form->save($record)
Precisa setar campos entre fill e persistir Setar ANTES do save()
Sem campos de arquivo save() funciona igual (fillRecord + $record->save())
// RECOMENDADO: save() encapsula tudo
$record->negociacao_id = $this->negociacaoId; // setar antes
$this->form->save($record);

// ALTERNATIVA: controle fino
$this->form->fillRecord($record);
$record->campo_extra = 'valor';
$record->save();

Quando usar cada componente

Preciso... Usar
Upload unico no disco <mad-file-field storage="disk">
Upload unico como BLOB no banco <mad-file-field storage="db">
Upload multiplos (virgula) <mad-multi-file-field storage="disk" mode="comma">
Upload multiplos (tabela, disco) <mad-multi-file-field storage="disk" mode="table">
Upload multiplos (tabela, BLOB) <mad-multi-file-field storage="db" mode="table">
Upload de imagem com crop/camera <mad-image-field> (ver image-field.md)
Botao compacto de anexar (toolbar, sem dropzone) <mad-attach-btn> (ver attach-btn.md)

NUNCA fazer

{{-- ERRADO: usar mad-input-field com type="file" --}}
<mad-input-field name="arquivo" label="Arquivo" type="file" />

{{-- CERTO --}}
<mad-file-field name="arquivo" label="Arquivo" storage="disk" folder="uploads/docs" />

{{-- ERRADO: codigo de arquivo manual no controller --}}
move_uploaded_file($_FILES['arquivo']['tmp_name'], $dest);
$record->arquivo = $dest;

{{-- CERTO: save() faz tudo --}}
$this->form->save($record);

{{-- ERRADO: fillRecord+save() manual quando form->save() resolve --}}
$this->form->fillRecord($record);
$record->save();

{{-- CERTO --}}
$this->form->save($record);

{{-- ERRADO: ler $_FILES e tratar upload na mao na view --}}
<input type="file" name="arquivo">
@php /* ...mover $_FILES['arquivo'] manualmente no controller... */ @endphp

{{-- CERTO --}}
<mad-file-field name="arquivo" label="Arquivo" storage="disk" folder="uploads/docs" />