mad-multi-file-field
Upload múltiplos (mode comma|table).
Upload de multiplos arquivos com drag-and-drop, galeria de thumbnails e preview. Substitui <input type="file" multiple>. Para upload de um unico arquivo, ver <mad-file-field> em file-field.md.
IMPORTANTE: Sempre usar form->save($record) ao inves de fillRecord + $record->save() manual — o MadForm::save() encapsula tudo: preenche campos, salva os arquivos (disco ou BLOB), persiste ($record->save()), e roda os hooks pos-persistencia que gravam os paths/registros filhos.
Props
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
| name | string | '' | Nome do campo (obrigatorio, usa name[] automaticamente) |
| label | string | '' | Label do campo |
| width | string | '' | Largura inline do wrapper (100px, 50%) |
| max-width | string | '' | Largura maxima inline |
| 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"). Caminho relativo — absoluto, .. (path traversal) ou null byte sao rejeitados em tela (MadUploadPath::assertValidFolder) |
| mode | string | 'comma' | comma (CSV de paths, so storage="disk") ou table (registro por arquivo numa tabela filha) |
| model | string | '' | Classe do 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 do BLOB (db) na tabela filha (mode=table) |
| name-column | string | '' | Coluna para o nome original do arquivo (mode=table) |
| file-name | string | 'prefix' | Modo de geracao 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 |
mode="comma"so e processado no save quandostorage="disk"— comstorage="db"emode="comma"o save nao grava nada; usemode="table"para BLOB.
Onde os arquivos ficam — Storage/Flysystem
Com storage="disk", o save() grava pela API de Filesystem do Laravel, no
disco de uploads: default mad_uploads (root storage/app/mad), ou o disco
apontado por MAD_UPLOAD_DISK (config('mad.uploads.disk')) quando voce quer
S3 ou outro adapter. Nao ha fallback para a raiz do projeto — nao existe
mais escrita direta em filesystem.
O que vai pro banco continua sendo o caminho relativo (folder +
nome do arquivo), nunca o caminho absoluto — trocar de disco nao exige
migracao de dados. Para servir o arquivo, use o download assinado do framework
em vez de montar URL publica.
folder e validado por MadUploadPath::assertValidFolder: precisa ser
relativo; absoluto, .. (path traversal) ou null byte sao rejeitados.
Nomeacao do arquivo (file-name)
Mesma logica do <mad-file-field> (MadForm::_buildFileName()), aplicada a cada arquivo do lote.
| 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) |
<mad-multi-file-field name="anexos" label="Anexos"
storage="disk" folder="uploads/docs" file-name="original" />
Evento change (callback ao adicionar arquivos)
<mad-multi-file-field name="anexos" label="Anexos"
accept=".pdf,.csv" mad:change="onAnexosAdded" />
use Mad\Service\MadScratchStorage;
public function onAnexosAdded(array $tempKeys): void
{
foreach ($tempKeys as $key) {
$conteudo = MadScratchStorage::disk()->get($key); // ler cada arquivo
}
$this->form->set('total_anexos', count($tempKeys));
}
O callback recebe chaves relativas de Storage, nao caminhos absolutos de
filesystem: o MadComponentHandler faz o staging do $_FILES no disco de
scratch mad_tmp (storage/app/mad-tmp) sob o prefixo mad_uploads/, e passa
mad_uploads/<uniqid>_<nome> para a acao. Leia sempre via
MadScratchStorage::disk() (ou Storage::disk('mad_tmp')) — nunca com
file_get_contents() em caminho montado a mao. Arquivos staged com mais de 1h
sao removidos por limpeza probabilistica.
Funciona com auto-bind (void) ou MadResponse — mesma assinatura de
mad-file-field e mad-image-field, mas recebendo array $tempKeys (multi)
ao inves de string $tempKey (single).
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'
use Illuminate\Support\Facades\DB;
// Controller — zero codigo de arquivo
DB::connection('business')->transaction(function () {
$record = MeuModel::findOrNew($this->registroId);
$this->form->save($record);
$this->registroId = (int) $record->id;
});
Na edicao, o componente carrega os arquivos existentes (CSV de paths) e mostra na galeria; o que o usuario remover/mantiver volta como hidden inputs (__mad_existing_files) e o save() reconcilia (apaga do disco os removidos, mantem os demais, acrescenta os novos).
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'(nome original sanitizado)
Na edicao, o componente carrega os arquivos ja vinculados via o model+foreign-key+path-column declarados, e reconcilia remocoes/mantidos do mesmo jeito que o modo comma.
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 — save() nao faz nada com o campo.
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 ou BLOB | <mad-file-field> (ver file-field.md) |
| Upload multiplos (CSV de paths) | <mad-multi-file-field storage="disk" mode="comma"> |
| Upload multiplos (tabela filha, disco) | <mad-multi-file-field storage="disk" mode="table"> |
| Upload multiplos (tabela filha, BLOB) | <mad-multi-file-field storage="db" mode="table"> |
| Upload de imagem com crop/camera | <mad-image-field> (ver image-field.md) |
NUNCA fazer
{{-- ERRADO: input nativo multiplo --}}
<input type="file" name="anexos[]" multiple>
{{-- CERTO --}}
<mad-multi-file-field name="anexos" label="Anexos" storage="disk" folder="uploads/docs" />
{{-- ERRADO: codigo de arquivo manual no controller quando storage ja resolve --}}
foreach ($_FILES['anexos']['tmp_name'] as $i => $tmp) {
move_uploaded_file($tmp, $dest);
}
{{-- CERTO: save() faz tudo --}}
$this->form->save($record);
{{-- ERRADO: usar mode="comma" esperando funcionar com storage="db" --}}
<mad-multi-file-field name="anexos" storage="db" mode="comma" />
{{-- mode=comma so e processado quando storage="disk" --}}
{{-- CERTO: BLOB exige mode=table --}}
<mad-multi-file-field name="anexos" storage="db" mode="table"
model="Anexo" foreign-key="registro_id" path-column="conteudo" />
{{-- ERRADO: persistir os arquivos filhos manualmente quando mode=table resolve --}}
$this->form->save($record);
foreach ($_FILES['arquivos']['tmp_name'] as $i => $tmp) {
Anexo::create(['registro_id' => $record->id, 'conteudo' => $tmp]);
}
{{-- CERTO: declarar mode="table" no Blade, save() cria os registros filhos --}}
<mad-multi-file-field name="arquivos" storage="disk" mode="table"
model="Anexo" foreign-key="registro_id" path-column="conteudo" />