plumtreesystems/file-bundle
PlumtreeSystems FileBundle is a Symfony/PHP package for adding file management to applications, with bundle integration for handling uploads, storage paths, and file-related workflows so teams can avoid building custom file handling from scratch.
Architecture fit. 3.0.0 removes any lingering doubt about what this package is: symfony/* at >=7.2 is a hard floor, which is only expressible by something that is genuinely part of the Symfony framework ecosystem, not a framework-agnostic library wearing a bundle wrapper. The -bundle suffix, the DI extension, and now a Symfony 7.2 minimum together confirm the integration model is the Symfony kernel and its container — neither of which Laravel runs. Our original two-path assessment narrows: path (b) "framework-agnostic core shipped as a bundle" is now less likely, because a package that gates on Symfony 7.2 is usually consuming Symfony 7.2 APIs (HttpFoundation upload objects, File/Path helpers, Mime component, possibly the new Symfony\Component\Filesystem\Path normalisation) rather than merely being distributed alongside them. Path (a) — manual instantiation and container binding — remains the only route, and its cost goes up, not down.
Integration feasibility. Mechanically still trivial: Composer, MIT, PSR-4. But the constraint set is now the deciding factor, and it is hostile to most of our estate. PHP >=8.2 drops Laravel 10 on PHP 8.1 outright. Symfony >=7.2 is the sharper problem: Laravel 10 pins Symfony components at ^6.2 (hard conflict, unresolvable), and Laravel 11 allows ^7.0, so it can technically resolve to 7.2 but only if every other Symfony-adjacent dependency in the graph tolerates the bump — and any package pinning 7.0.* or 7.1.* will deadlock the resolver. Realistically this package is installable only on Laravel 12-class applications. That is a compatibility fact we did not have before, and it changes the population of apps this can serve from "all of them" to "the newest slice."
Technical risk. Mixed, and worth separating. Improved: dropping doctrine/orm is a genuine architectural win — it removes entity mapping, a second persistence layer, and the implicit assumption that file metadata lives in Doctrine-managed tables. Dropping knplabs/gaufrette and gaufrette/aws-s3-adapter removes a competing filesystem abstraction that would have collided with Laravel's Flysystem-backed Storage. Both removals make the package less opinionated about persistence and storage, which is exactly the direction we wanted. Worsened: this is a breaking major with at least two dependency families deleted and no stated replacement in the notes. "Drop Doctrine ORM" could mean metadata persistence is now behind an interface we implement (good, and a real integration point to design), or that the feature is simply gone (bad, and a signal of scope contraction). Zero dependents and a one-star footprint still mean no ecosystem validation, but the changelog link to 2.3.0 corrects one earlier assumption: there is a version history and therefore some cadence — the prior line was maintained to 2.3.0 before this bump. The 0.305 composite and elevated opportunity score still describe an unproven package with a real gap, now with an additional churn signal on top.
Key questions.
ContainerAwareInterface / event-dispatcher couplings in the paths we care about?Stack fit. The compatible surface has narrowed to Laravel 12 / PHP 8.2+. For any app on Laravel 10 or 11, the decision is effectively made for us and we should say so plainly rather than spending a spike on a resolver conflict we can predict. For the Laravel 12 slice, the value proposition is unchanged but the dependency story is better: two heavyweight, opinionated dependencies are gone, so what remains is closer to a file-lifecycle utility than a mini-framework. The honest framing stands — we are judging whether these abstractions beat the glue we would write ourselves, and with 3.0.0 we are also judging whether a package that just deleted two dependency families has finished its own refactor.
Migration path. Do not chase 3.0.0 blindly, and do not treat 2.3.0 as the safe harbour either — it carries Doctrine ORM and Gaufrette, which is the heavier of the two propositions. Pin to a fork via a Composer path/VCS repository so we control the resolved version and can relax the Symfony constraint if the audit shows it is incidental rather than real. Keep the internal FileStore interface as the boundary; the package's types must not leak. Note the new integration point created by the Doctrine removal: if metadata persistence is now an interface, our adapter must implement it, and that implementation is ours to test, not theirs.
Compatibility. This is now the primary gate and it is cheap to run — do it before anything else. composer why-not against the Laravel 12 graph, then repeat against 11 and 10 to confirm the expected failures and record them. Explicitly check the resolved Flysystem major after Gaufrette's removal, since Laravel pins one. Explicitly check whether the Symfony 7.2 floor is satisfiable alongside every other Symfony-consuming package we ship. If the answer is "Laravel 12 only," that is a scoping decision for the product owner, not a technical one to bury.
Sequencing.
composer.json, and tests. Answer the two replacement questions — what stands in for Gaufrette and for Doctrine ORM.FileStore; land the native Laravel Storage implementation alongside so the fallback is real.Maintenance. Still fork-and-own from day one, and 3.0.0 raises the cost slightly: we are now inheriting a codebase mid-refactor, with two dependency families recently excised and their replacements undocumented in the release notes. Pin the version, name an owner, watch upstream, and document the patch process — but add a specific item: track whether upstream follows 3.0.0 with a 3.1 that settles the replacement story, or goes quiet. A major version followed by silence is a worse signal than no major version at all.
Support. Unchanged and unchanged for a reason: no vendor, no SLA, no commercial backing. The 3.0.0 breaking change is a concrete demonstration of why that matters — an upstream maintainer made a decision that invalidates our supported-version matrix overnight, and our only recourse is a fork. Acceptable for internal plumbing; not acceptable anywhere an auditor asks who maintains the code writing customer files.
Scaling. Unverified, and now with a new unknown: removing Gaufrette means the storage path changed, so any performance characteristics we might have assumed from that library no longer apply. Load-test memory on large uploads (streaming versus buffering is still unanswered), concurrency against one backend, and whether writes are synchronous when they should be queued. Do not extrapolate from the pilot.
Failure modes. Prior list stands — orphaned objects on rollback, partial writes, path traversal, client-trusted MIME validation, credential rotation, config divergence between Symfony defaults and Laravel disk config. 3.0.0 adds three: (a) a half-migrated persistence layer, where file metadata is written by our adapter but read by nothing, or vice versa; (b) a resolver that succeeds on Laravel 12 in isolation but breaks the moment another package pins Symfony 7.0/7.1 — verify in the full graph, not a scratch project; (c) an upgrade path where 2.x is EOL and the only forward route is a 3.0.0 whose own migration story we do not know. Each needs an explicit adapter-layer test.
Ramp-up. Roughly unchanged for the API surface, slightly higher for the migration story: a developer now has to read both the DI extension and the 2.3.0→3.0.0 diff to understand what was removed and why. One engineer-day to a working spike assuming the resolver cooperates; three to five to production-grade with the adapter, the fallback implementation, and the persistence-interface work the Doctrine removal implies. The posture is unchanged — prove-then-adopt, exit pre-built — but the exit is now cheaper to justify, because the package has told us exactly how it will break our build.
How can I help you explore Laravel packages today?