plusforta/storage-bundle
Plusforta Storage Bundle provides a flexible, configurable file storage layer for Symfony applications. It offers a clean service-based API for uploading, retrieving, organizing, and deleting files, with support for multiple storage backends and easy integration into existing PHP projects.
Install it: composer require plusforta/storage-bundle. If you're on a recent Symfony Flex setup, the recipe (if available) registers the bundle automatically in config/bundles.php; if not, add Plusforta\StorageBundle\StorageBundle::class => ['all' => true], manually.
Find the config surface first: before writing any code, locate the bundle's DependencyInjection/Configuration.php (or Configuration class) in vendor/plusforta/storage-bundle. Its extension alias tells you the root config key (likely something like plusforta_storage). This is the single most important file to read — it lists every adapter type, option, and default the bundle supports.
Minimal config: create config/packages/plusforta_storage.yaml with one filesystem. A typical shape:
plusforta_storage:
filesystems:
default:
adapter: local
options:
directory: '%kernel.project_dir%/var/storage'
Run bin/console debug:container --tag= and bin/console debug:config <alias> to confirm the service IDs it registers.
First use case: inject the resulting Flysystem FilesystemOperator into a service and write a file:
public function __construct(private FilesystemOperator $defaultStorage) {}
public function store(string $path, string $contents): void
{
$this->defaultStorage->write($path, $contents);
}
If you're a Laravel dev, think of Storage::disk('default')->put(...) — that's the mental model. There is no Storage facade, no Storage::fake(); you get a FilesystemOperator service injected via autowiring or a named service parameter.
filesystems: and let the bundle register one FilesystemOperator per name. Inject by service ID (@plusforta_storage.filesystem.default) or use a named autowiring alias if the bundle provides one (check Resources/config/services.xml for ->alias() / ->public() declarations).League\Flysystem\FilesystemOperator rather than the concrete Filesystem. That keeps your domain services testable and swap-friendly.config/filesystems.php; here it's the bundle's config tree — same idea, different syntax.FilesystemOperator with new League\Flysystem\Filesystem(new InMemoryFilesystemAdapter()) and test against that. Don't boot the kernel for pure storage unit tests.Psr\Container\ContainerInterface only for the storage service locator the bundle exposes — not the whole app container.FilesystemOperator in a small StorageService of your own that handles path prefixing, naming conventions, and domain-specific operations. This keeps the bundle boundary thin and makes it trivial to swap to Laravel's Storage if you're running a hybrid app.league/flysystem, not a mature framework component. Everything it does, you can do with a couple of Flysystem service definitions yourself. Budget for auditing the bundle source and pinning versions.League\Flysystem\FilesystemInterface) vs v2/v3 (FilesystemOperator) is a breaking change in method names (put vs write, has vs fileExists). Verify which league/flysystem major the bundle requires in its composer.json — the docs you read online may not match.composer.json for the minimum Symfony version the bundle supports. Older bundles break on Symfony 6/7 due to autowiring and container changes; if it's unmaintained, you may need a replace/fork or a local path repository.Storage::fake() equivalent: unlike Laravel, there's no built-in test double. Roll your own InMemoryFilesystemAdapter-backed service or use FilesystemOperator mocks. Don't rely on the bundle booting in tests.debug:config gives "no extension", the bundle isn't registered yet, or the alias differs from the package name. Grep the bundle source for getAlias().public/private) must be set per-write or via adapter defaults. This is the same gotcha as Laravel's Storage::put with a visibility argument — but here there is no 'public' default coming from a disk config, so be explicit.Storage facade muscle memory: there's no global helper. If you find yourself missing Storage::disk(...)->..., build a small facade-like wrapper service so the ergonomics match — that's the pragmatic "extension point" for this bundle.bin/console debug:container <id> --show-arguments and bin/console lint:container are faster than reading stack traces.Filesystem services directly in your own services.yaml and drop the bundle. Keep the bundle only if its config shape and service naming match your team's conventions.How can I help you explore Laravel packages today?