roundly-consulting/media-library-for-laravel
A Laravel media library for attaching files to Eloquent models. Upload and store images or documents, generate conversions and responsive thumbnails, organize media in collections, and manage custom properties through a clean, fluent API. Supports local and cloud disks for flexible media management.
replace() paths so that identical files are shared safely across rows. The decision to store one original and point many records at it (rather than duplicating bytes per record) is now defensible under concurrent writes, not just in single-threaded tests.forceDelete() inside a rolled-back transaction permanently losing files while the row returns, and replace() overwriting bytes a soon-to-be-committed row was about to share. For a PM, this converts "media is best-effort" into "media is transactionally consistent with the database," which is a claim you can put in front of customers.DB::transaction() alongside domain writes and expect rollback semantics to hold for both row and bytes. This unblocks features like order-attachment flows, draft-to-published content, and import jobs that must be all-or-nothing.ModelNotFoundException on racing attach/copy/replace) are now explicit and testable. Worth encoding as acceptance criteria in your own integration suite rather than trusting upstream alone — and now worth doing against our model class, not a stand-in.Consider it when you are on Laravel, need media/original/variant handling with checksum-based deduplication, and your writes happen concurrently or inside database transactions — queues, multi-worker consumers, bulk imports, or multi-tenant uploads. It is a particularly strong fit if you have force-delete/cleanup requirements (GDPR erasure, retention policies) that must not strand or over-delete bytes, or if you intend to subclass the media model for per-tenant behavior and want the package's test tooling to keep working when you do.
Look elsewhere, or scope down, when:
SELECT ... FOR UPDATE is a no-op or unsupported.replace() keeps the same URL and discards old bytes on commit; if you need old versions, that is your own responsibility and a different tool.Adoption criteria before promoting to production: confirm the underlying DB locking works as intended, run a concurrency test that exercises dedup + force-delete and replace + rollback, ensure any code catching media exceptions handles ModelNotFoundException from racing attach()/copy()/replace() — that is new, surfaced behavior your error paths must tolerate — and, if you swap the model, re-run your factory-based fixtures to confirm they now resolve to your class.
To executives: A patch release that closes silent data-loss bugs in the media layer — no API changes, no migration work, no user-visible behavior change except correct outcomes. The commercial value is in what it prevents: files disappearing while records still reference them, orphaned bytes surviving a rolled-back delete (a compliance and storage-cost issue), and files being overwritten by concurrent writes. A follow-on fix also makes the package's test scaffolding respect our own model configuration, which lowers the cost of tailoring media storage to our tenancy model. If we handle customer uploads at any concurrency, run any bulk import alongside live traffic, or plan to customize the media model, we should take this before those workloads start, not after.
To engineering: Four fixes, all in the concurrency/transaction boundary, plus one factory fix. Sharing rows now save under a row lock on the row they share with, so a concurrent force-delete waits and then sees them; if the shared row is gone, dedup add/replace falls back to storing its own copy and attach()/copy() throw ModelNotFoundException. Force-deleted files are now removed on commit, immediately outside a transaction, and retained on rollback. replace() locks its own row and does a locking read for other referrers, writing to a new path when the file is shared, and restores old bytes on rollback for in-place replaces. Separately, factories now instantiate the model configured on the host, so subclassed or swapped models are built correctly in tests. Action items for us: upgrade to 1.3.1 (reviewing the 1.2.x/1.3.x changelogs once, since we are coming from 1.1.4), add ModelNotFoundException handling to media error paths, and add integration coverage for dedup-plus-force-delete and replace-plus-rollback against our actual database engine using our own model class.
How can I help you explore Laravel packages today?