Download the PHP package risetechapps/media-for-laravel without Composer
On this page you can find all versions of the php package risetechapps/media-for-laravel. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download risetechapps/media-for-laravel
More information about risetechapps/media-for-laravel
Files in risetechapps/media-for-laravel
Package media-for-laravel
Short Description Laravel package for advanced media and upload management, with exact storage accounting.
License MIT
Homepage https://github.com/risetechapps/media-for-laravel
Informations about the package media-for-laravel
Laravel Media
📌 Sobre
Gerenciamento de mídia para Laravel com contabilidade exata de bytes.
Diferente de bibliotecas que só registram o tamanho do arquivo original, este pacote registra cada arquivo físico — original, conversões e variantes responsivas — numa tabela media_files, e mantém em media.total_size a soma real ocupada em disco. É isso que torna possível reportar e cobrar storage com precisão.
Traz ainda dois recursos montados sobre essa contabilidade:
- Escopo por contexto (multi-tenant desacoplado) — particiona a mídia por um contexto que o pacote não precisa conhecer (sem coluna
tenant_id, sem depender de nenhum pacote de tenancy). - Cota de storage — barra o upload antes de gravar quando o contexto estouraria o limite.
✨ Funcionalidades
- 📤 Upload direto ou por request/disco/URL, com validação por coleção.
- 🗂 Coleções com regras (arquivo único, mime types, fallback).
- 🖼 Conversões (miniaturas) para imagem, PDF, vídeo e ícone por tipo de arquivo — enfileiráveis.
- 📱 Imagens responsivas (
srcset), desligáveis por config. - 🔗 URL trocável (CDN) com cache de URL assinada S3.
- 📊 Relatórios de storage por dono, coleção e total.
- 🏢 Escopo por contexto (tenancy desacoplado) com filtro fail-closed.
- 🚦 Cota de storage por contexto ou fixa.
- ♻️ Uploads temporários e prune automático.
- ☁️ Compatível com S3 e serviços compatíveis (iDrive e2, etc).
🚀 Instalação
Requisitos
- PHP >= 8.4
- Laravel >= 12
- Imagick (com Ghostscript p/ PDF; com libheif p/ HEIC) — opcional, cai em ícone sem ele
- ffmpeg + ffprobe no PATH — opcional, p/ miniatura de vídeo
- Binários otimizadores (
jpegoptim,optipng,pngquant,gifsicle,cwebp) — opcional, p/optimize()
Pacote
Publicar a config (opcional):
🔄 Atualizando da 1.x / 2.x
A 3.x reescreve o schema (contabilidade de bytes). O upgrade é automático e preserva os dados — as mídias antigas continuam servíveis, sem mover nada no bucket.
O migrate roda duas migrations de upgrade: convertem a tabela media (PK bigint → uuid, removem generated_conversions, adicionam total_size, json → jsonb) e registram o arquivo original de cada mídia em media_files. Instalações novas ignoram essas migrations automaticamente.
A conversão promove a coluna
uuidlegada à nova chave primáriaid. Como o layout de arquivos antigo já era{coleção}/{uuid}/…, os caminhos físicos continuam válidos — por isso nada precisa ser copiado.
media:reconcile — fechando a contabilidade
O upgrade contabiliza os originais (o único tamanho guardado no banco). Conversões e variantes responsivas herdadas ocupam storage, mas o tamanho delas só existe em disco. O media:reconcile varre o diretório de cada mídia, registra esses arquivos em media_files e recalcula total_size:
Idempotente e não-destrutivo: só lê tamanhos e escreve no banco, nunca move ou apaga arquivos. Enxerga todas as mídias (todos os escopos, inclusive as em lixeira). Serve também como reconciliação geral, caso algum byte tenha sido escrito fora do MediaFilesystem.
⚠️ A migration de upgrade dropa a coluna
id(bigint) sem CASCADE — se outra tabela referenciarmedia.idpor foreign key, omigratefalha de propósito, em vez de quebrar a integridade em silêncio. Migre essas referências para o novoid(uuid) antes de atualizar.
🧩 Configuração do Model
Implemente o contrato e use a trait:
Atalho: HasMediaSuite
Para o caso comum (uma coleção padrão + uma conversão thumb), a trait HasMediaSuite já traz tudo pronto — sem repetir o boilerplate:
Os defaults saem de config('media.defaults') (coleção uploads, thumb 368×232 webp q80, orientation() + optimize(), enfileirada). Sem prender você:
Sem worker? Defina
MEDIA_DEFAULT_CONVERSION_QUEUED=false(ou sobrescrevadefaultConversionQueued()) — a conversão roda no próprio request. Para trocar tudo, sobrescrevaregisterMediaCollections()/registerMediaConversions()normalmente.
📤 Adicionando mídia
Proteção contra SSRF:
addMediaFromUrl()só baixa de destinos públicos. Antes do download (e a cada redirecionamento) o host é resolvido e recusado comUnsafeMediaUrlExceptionse apontar para loopback, rede privada, link-local/metadados da nuvem (169.254.169.254), CGNAT ou faixas reservadas; sóhttp/https, sem credenciais na URL. A conexão é fixada no IP validado. Para restringir aos hosts do seu storage, useMEDIA_DOWNLOAD_ALLOWED_HOSTS; para liberar um host interno legítimo (ex.: MinIO),MEDIA_DOWNLOAD_TRUSTED_PRIVATE_HOSTS.
O FileAdder é fluente:
📥 Lendo mídia
Removendo:
🖼 Conversões
Definidas em registerMediaConversions() via addMediaConversion():
| Método | Efeito |
|---|---|
width(int) / height(int) |
Dimensão alvo |
fit(Fit, w, h) |
Modo de encaixe (Crop, Contain, Max, …) |
format(string) |
webp, png, jpg, … |
quality(int) |
Qualidade 0–100 |
sharpen(float) |
Nitidez |
background(string) |
Cor de folga (vazio = transparente) |
pdfPageNumber(int) |
Página do PDF |
optimize() |
Passa por otimizadores (corta bytes) |
orientation() |
Corrige EXIF (foto deitada) |
queued() / nonQueued() |
Fila ou na hora |
performOnCollections(...) |
Restringe a coleções |
Geradores consultados em ordem (config media.conversions.generators): imagem → PDF → vídeo → ícone. O ícone aceita qualquer coisa e é o último recurso, garantindo miniatura para qualquer tipo.
📱 Imagens responsivas (srcset)
Desligado por padrão. Cada largura é outro arquivo ocupando storage.
Precisa das duas chaves (config global + opt-in na coleção). Gera larguras menores que a original (nunca amplia), cada uma contabilizada.
🔗 URL de exibição, CDN e endpoint
getFullUrl() é a URL que o front consome. O comportamento depende da config:
Padrão — disco (S3 assinado + cache)
Sem CDN, discos S3 assinam a URL e a reaproveitam por alguns minutos (media.url.signed_cache_minutes). É a entrega segura para conteúdo privado: a URL expira, ninguém acessa sem ela.
O domínio do bucket vem do próprio driver S3 — para usar um endpoint/domínio branded, configure o disco (AWS_ENDPOINT), não o CDN:
CDN público (built-in)
Com media.cdn.base preenchido, getFullUrl() passa a montar a URL pública do CDN (sem assinatura), sem trocar de gerador:
⚠️ CDN serve URL pública, sem assinatura. Só use para conteúdo que pode ser público (catálogo, banner). Para mídia privada de tenant, deixe
cdn.basevazio e sirva assinado. Apontarcdn.basepara o endpoint S3 privado gera URL que não abre (bucket exige assinatura). CDN de conteúdo privado exige assinatura na borda (CloudFront/Cloudflare com signed URL) — nesse caso, implemente umUrlGeneratorContractpróprio que assina.
Gerador próprio
Para lógica de URL totalmente custom, implemente UrlGeneratorContract e aponte em config('media.url_generator'):
📊 Relatórios de storage
Contabilidade global (ignora o escopo/partição). Via facade Media:
Atalhos no model:
Por padrão inclui mídia na lixeira (ainda ocupa disco). Passe false para só ativo:
Formatação com Size
🏢 Escopo por contexto (tenancy desacoplado)
Particiona a mídia por um contexto que o pacote não conhece. Sem coluna tenant_id: o contexto é carimbado em custom_properties._scope na criação e filtrado em toda consulta por um global scope fail-closed.
Registrando o resolver
Implemente MediaScopeResolver (o consumidor sabe o que é o contexto):
Comportamento
- Com contexto → só a mídia carimbada com aquele contexto.
- Sem contexto (
resolve()vazio) → fail-closed: só mídia sem escopo. Nunca vaza mídia de outro contexto. - Sem resolver → o pacote roda sem particionar nada.
Ignorar a partição (admin/relatório):
⚠️ Segurança. Mantenha o tipo consistente no resolver:
sub_tenant_id => 42(int) carimba número; consultar com'42'(string) não casa nojsonb. Eunscoped()fura a partição de propósito — use só em contexto administrativo.
🚦 Cota de storage
Barra o upload antes de gravar quando uso + tamanho > limite. O uso é o total do contexto atual (mesmo escopo acima). Duas formas de definir o limite, nesta prioridade:
1. Resolver (dinâmico, por contexto) — vence sempre:
2. Fixo (config/env) — usado quando não há resolver. Aceita bytes ou string legível:
Consultando
Tratando o estouro
A cota é checada no original. Conversões e variantes responsivas entram depois (async) e não falham retroativamente — são o custo do original aceito.
♻️ Uploads temporários
Registre a rota (middleware/auth por sua conta via $options):
Depois vincule ao model definitivo:
Uploads temporários expiram em 2 dias; mídia em lixeira é removida após 180 dias (config media.expiration). A limpeza remove os registros e os arquivos em disco.
Agendamento (prune)
O package agenda um model:prune diário para os seus models (Media e MediaUploadTemporary) — como são models de package, o model:prune não os descobre sozinho, então as classes são passadas explicitamente.
Requer o cron do Laravel ativo:
⚠️ Prune em multi-tenant
O agendamento do package roda na conexão central. Em setups database-per-tenant, os registros vivem no banco de cada tenant — o model:prune na central não os alcança.
Nesse caso, desligue o agendamento do package e rode o prune dentro do contexto de cada tenant, pelo mecanismo do seu pacote de tenancy:
⚙️ Referência de configuração
| Chave | Env | Padrão | Descrição |
|---|---|---|---|
disk.name |
MEDIA_DISK |
local |
Disco de armazenamento |
disk.prefix |
STORAGE_PREFIX |
'' |
Prefixo de root (disco isolado) |
path_generator |
— | DefaultPathGenerator |
Layout dos arquivos |
url_generator |
— | DefaultUrlGenerator |
Resolução de URL (trocável p/ CDN) |
url.signed_cache_minutes |
MEDIA_URL_SIGNED_CACHE_MINUTES |
55 |
Cache da URL assinada S3 |
url.signed_ttl_minutes |
MEDIA_URL_SIGNED_TTL_MINUTES |
60 |
Validade da assinatura |
cdn.base |
MEDIA_URL_GENERATOR_CDN_BASE |
null |
Host do CDN público; vazio = serve do disco |
cdn.include_disk_root |
MEDIA_CDN_INCLUDE_DISK_ROOT |
true |
Inclui o root do disco na chave do CDN |
conversions.generators |
— | Image, Pdf, Video, FileIcon | Cadeia de geradores |
conversions.video_frame_second |
MEDIA_VIDEO_FRAME_SECOND |
1 |
Segundo do quadro do vídeo |
defaults.collection |
MEDIA_DEFAULT_COLLECTION |
uploads |
Coleção padrão do HasMediaSuite |
defaults.conversion.* |
MEDIA_DEFAULT_CONVERSION_* |
thumb 368×232 webp q80 | Conversão padrão do HasMediaSuite |
defaults.conversion.queued |
MEDIA_DEFAULT_CONVERSION_QUEUED |
true |
false roda a conversão no request |
responsive_images.enabled |
MEDIA_RESPONSIVE_IMAGES |
false |
Master switch do srcset |
responsive_images.widths |
— | [1920…320] |
Larguras alvo |
scope.resolver |
— | null |
MediaScopeResolver (tenancy) |
quota.resolver |
— | null |
QuotaResolver |
quota.default |
MEDIA_QUOTA_DEFAULT |
null |
Limite fixo (bytes ou '10GB') |
upload.max_size |
MEDIA_UPLOAD_MAX_SIZE |
51200 |
KB máx. no endpoint |
download.timeout |
MEDIA_DOWNLOAD_TIMEOUT |
30 |
Timeout de addMediaFromUrl |
download.allowed_hosts |
MEDIA_DOWNLOAD_ALLOWED_HOSTS |
vazio | Se definido, só esses hosts em addMediaFromUrl (.dominio = subdomínios) |
download.trusted_private_hosts |
MEDIA_DOWNLOAD_TRUSTED_PRIVATE_HOSTS |
vazio | Hosts internos liberados para IP privado (ex.: MinIO) |
download.max_redirects |
MEDIA_DOWNLOAD_MAX_REDIRECTS |
3 |
Redirecionamentos seguidos (cada destino revalidado) |
expiration.temporary_uploads |
MEDIA_TEMPORARY_UPLOADS_EXPIRATION_DAYS |
2 |
Dias p/ prune de temporários |
expiration.soft_deleted |
MEDIA_SOFT_DELETED_EXPIRATION_DAYS |
180 |
Dias p/ prune de lixeira |
prune.enabled |
MEDIA_PRUNE_ENABLED |
true |
Agenda o model:prune diário (desligue em multi-tenant) |
prune.time |
MEDIA_PRUNE_TIME |
02:00 |
Horário do prune diário |
🧠 Como a contabilidade funciona
Todo byte entra e sai por um único ponto (MediaFilesystem). Cada escrita grava o arquivo + registra a linha em media_files + atualiza total_size; cada remoção inverte. Escrever direto no Storage, contornando esse serviço, fura a contagem.
media_files guarda cada variante: original, conversion:{nome}, responsive:{largura}. media.total_size é o cache da soma — a fonte da verdade é media_files.
📄 Licença
MIT. Veja LICENSE.
All versions of media-for-laravel with dependencies
illuminate/bus Version ^12.0
illuminate/database Version ^12.0
illuminate/http Version ^12.0
illuminate/queue Version ^12.0
illuminate/support Version ^12.0
league/flysystem-aws-s3-v3 Version ^3.0
php-ffmpeg/php-ffmpeg Version ^1.3
risetechapps/has-uuid-for-laravel Version ^1.2
risetechapps/monitoring-for-laravel Version ^5.0.0
risetechapps/risetools Version ^3.1.0
spatie/image Version ^3.9
spatie/pdf-to-image Version ^3.0
spatie/temporary-directory Version ^2.4
symfony/mime Version ^7.0
tpetry/laravel-postgresql-enhanced Version ^3.7.0