kitpages/file-system-bundle
Kitpages FileSystemBundle is a Symfony bundle for working with files and directories through reusable filesystem services and helpers, simplifying common storage and file-management tasks in your applications.
Architecture fit — this is a category mismatch and should be the first line of the assessment. Despite being presented as a Laravel/PHP package, kitpages/file-system-bundle is a Symfony 2-era bundle: it wires itself through services.xml, Symfony's DI container, and optionally the EventDispatcher. There is no ServiceProvider, no facade, no publishable config, no config/*.php. Composer will autoload the classes into a Laravel app, but nothing will be bound into the container. Every adapter would require a hand-written bridge. The genuinely portable asset is the design pattern — a thin adapter contract (set/get/delete/exists over a path namespace) where the content argument is a file path or stream handle rather than a string, so a 2 GB transfer never materializes in a PHP variable. That idea is worth adopting. The code is not.
Integration feasibility. Direct installation is low-feasibility. Expect pinned symfony/* ~2.x constraints that will conflict with the Symfony 6/7 components Laravel 10/11 ship, PSR-0 or early-PSR-4 autoloading, PHP 5.3–5.6 idioms, and an S3 adapter built against AWS SDK v1/v2 with its own CVE history. Concept extraction is moderate-feasibility and is the recommended route. A notable tell: the developer-facing adoption guide in our source set documents Symfony bundle registration in AppKernel.php, not a Laravel install — the documented path is itself evidence of stack mismatch.
Technical risk.
Key questions.
Storage::readStream/writeStream already covers both, so the answer determines whether this pattern is still needed at all.Stack fit. Laravel's Storage facade is a Flysystem wrapper and is the idiomatic home for this problem. Local-to-local moves use stream_copy_to_stream between handles to keep memory flat. For S3, Storage::writeStream() plus a multipart uploader — or better, a presigned PUT/GET URL that bypasses the PHP process entirely. The bundle's differentiator is achievable natively on a maintained dependency. Do not introduce a second storage abstraction alongside Storage; the onboarding tax is permanent and the marginal capability is zero.
Migration path.
FileStoreInterface with putStream(string $path, resource $stream), getStream(string $path): resource, delete, exists, url — typed for PHP 8.LocalFileStore (temp file + rename() for atomicity) and S3FileStore (multipart, StreamInterface over fopen).file_store config block.composer.json.Compatibility. Target PHP 8.1+, Laravel 10/11, Flysystem 3.x, AWS SDK v3. Do not attempt to co-install the original Symfony 2 constraints — the resolver will fight you and the win is negligible.
Sequencing. Contract → adapters → provider/config → call-site migration → decommission. Front-load the spike and the contract; the rest is mechanical. Exit criteria up front: if the spike shows Storage::writeStream already meets the measured memory target at p99 file size, skip steps 2–3 and close the evaluation as "solved by existing dependency." Write that down before the spike starts, because the temptation to build the abstraction anyway is strong.
Maintenance. Upstream maintenance is effectively zero. Assume no patches, no PHP 8 fixes, no security response. Adoption implies a permanent fork owned by our team, with all dependency-upgrade cost (AWS SDK, Symfony contracts) landing on us. A 12-star, zero-dependent package has no invisible maintainer community to absorb that. Budget it as a line item, not a footnote.
Support. Documentation is a README, likely French-language. No tests, no CI badge, no issue triage. Internal ramp-up on the abstraction itself is small — the surface is a handful of methods. The real cost is archaeological: reading Symfony 2 bundle conventions to reconstruct intent, and the ongoing "why aren't we using Storage?" question from every new engineer.
Scaling. A PHP-FPM worker is occupied for the full duration of a transfer; a 2 GB upload at 10 MB/s pins a worker for roughly 200 seconds. Under load, worker exhaustion hits well before memory exhaustion. Mitigations: presigned direct-to-store uploads, chunked/resumable transfers, queue workers for server-side moves, and a deliberate review of max_execution_time and request_terminate_timeout.
Failure modes to instrument.
rename() locally, multipart completion semantics on S3.Ramp-up. One to two engineer-weeks for the spike, contract, and both adapters for a mid-level engineer comfortable with streams, plus a half-day of team education on stream semantics and handle lifecycle (finally blocks, explicit closes). The conceptual surface is small; the risk sits entirely in the dependency's age, not its complexity.
Recommendation. Do not adopt the package. Treat it as a design reference and a benchmark oracle. Adopt the pattern — never hold full file content in a $content variable — only if the spike proves Laravel's native Storage::writeStream and presigned-URL paths are insufficient at our measured file sizes and concurrency.
How can I help you explore Laravel packages today?