erichard/dms-bundle
Symfony bundle for adding document management to your app: organize files in folder trees, persist document metadata with Doctrine, and integrate uploads, listings, and file access into your existing Symfony project.
Reality check first. erichard/dms-bundle is a Symfony 2 bundle — not a Laravel package. There is no ServiceProvider, no facade, no config publish tag, and its composer.json will constrain you to Symfony 2.x components and a PHP range that predates anything modern (symfony/framework-bundle ~2.x, PHP 5.x-era syntax). composer require erichard/dms-bundle in a Laravel app will not resolve, and even if you force it, you get a container-driven bundle that Illuminate cannot boot. So "leverage day-to-day" splits into two honest answers:
documents table, Intervention Image, queued thumbnail jobs). The design is genuinely worth reading; the code is not worth installing.Where to look first, in this order:
README.md — features, bundle registration, the config block for the filesystem and Imagine.Erichard/DmsBundle/Resources/config/services.xml — this is the real API surface. Every service id you'll inject (erichard_dms.document_manager and friends) is declared here.Entity/Document.php and the DocumentManager class — the persistence contract (what a document row actually stores) and the entry point for create/version/delete.Resources/config/routing.xml — the bundled download controller route, if you plan to let the bundle serve files.Minimal first use case (Symfony 2 host): register the bundle in AppKernel, import its routing, point the bundle's filesystem config at a Gaufrette filesystem service (local adapter to start), run doctrine:schema:update --force, then inject the document manager into one controller and persist a single UploadedFile. That's the whole happy path — one upload, one row, one file on disk.
Manager-as-single-entry-point. The bundle funnels everything through a DocumentManager service rather than letting controllers touch entities and storage separately. That is the pattern to copy regardless of framework: one class owns "write blob → create row → return document," so there is exactly one place where ordering can go wrong. In Laravel this is your DocumentService or the internals of a HasDocuments trait — not a controller.
Path-in-DB, bytes-in-storage. A document row stores a storage key/path, mime type, size, and metadata; the actual bytes live behind a Gaufrette adapter (local, S3, FTP). Keep that invariant strictly — it's what makes the storage backend swappable and what makes the Laravel port a 20-line Flysystem change instead of a rewrite.
Immutable versions, mutable pointer. Versioning is modelled as new rows referencing the parent document plus their own storage key, with the document itself holding "current." Copy this shape verbatim into a Laravel migration: a documents table and a document_versions table with document_id, storage_key, created_at, and a current_version_id on the parent. Never overwrite in place.
Derived artifacts are lazy and disposable. Thumbnails come from Imagine filters applied on demand and cached to the filesystem. The transferable lesson: treat thumbnails as regenerable cache, never as source of truth, and on Laravel move their generation into a queued job so upload requests stay fast.
Hierarchy via nodes. Organization is a DocumentNode tree rather than folders on disk. In Laravel, use parent_id + materialized path (or kalnoy/nestedset) and derive breadcrumbs from it — don't mirror the tree onto the filesystem, it always drifts.
Form/controller glue. The bundle ships a form type and a download controller, i.e. upload and retrieval are framework-citizens, not afterthoughts. The Laravel analogue is a StoreDocumentRequest for validation plus a DocumentController@download that authorizes before streaming — the bundle's route is where you check who may read what.
Cleanup ordering. Whichever direction you choose (file-then-row, or row-then-file), pick one and put compensation logic on the failure branch. This is the single most common source of orphaned blobs in DMS code.
filesystem config value is a service id, not a path. Passing a directory string fails at container compile or first use, not at boot — a confusing failure mode if you're new to Symfony DI.app/console debug:container erichard_dms to see wired services, doctrine:schema:validate after schema changes, and remember the compiled container in app/cache will hide config edits until cleared.league/flysystem disks + a documents/document_versions pair + intervention/image in a job + authorization policies on download. Libraries like spatie/laravel-medialibrary already solve 80% of this; the only thing worth deliberately copying from this bundle is the versioning table design and the "manager owns the write ordering" discipline.How can I help you explore Laravel packages today?