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:
fillRecord()detecta o campo file comstorageno schema- Salva o arquivo em
uploads/contratos/uniqid_nome.pdf - Seta
$record->arquivo = 'uploads/contratos/xxx.pdf' - Seta
$record->nome_arquivo = 'contrato.pdf'(name-column) $record->save()persiste no banco (Eloquent)- 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')(envMAD_UPLOAD_DISK). Vazio ⇒mad_uploadslocal. Aponte paras3/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_DISKapontando para disco inexistente emconfig/filesystems.phplanca excecao na resolucao — falha ruidosa, nao silenciosa. Eput()/putContent()lancamRuntimeExceptionquando o driver retornafalse, 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
outputDire a classeMadUploaderService. Codigo que dependia delas deve usarMadUploadStorage/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" />