swp/storage-bundle
swp/storage-bundle is a Symfony bundle that wires a configurable storage layer into your application, giving you one abstraction for reading, writing, and managing files across local and remote backends. Service-based configuration keeps storage concerns out of your application code.
First, understand what you're installing. swp/storage-bundle is a read-only subtree split of the Superdesk Web Publisher monorepo (SuperdeskWebPublisher/SuperdeskWebPublisher, under src/SWP/Bundle/StorageBundle). It is not an independently maintained library: there are no issues, PRs, or CI in this repo. Treat it as a Composer-visible slice of the main app, not a package you adopt on its own.
Minimal steps:
composer require swp/storage-bundle into an arbitrary Symfony app. Pull it transitively via swp/core-bundle (or the superdesk/web-publisher application skeleton) so the swp/* versions stay in lockstep. Mixing a lone swp/storage-bundle with an unrelated Symfony version is the fastest way to a broken container.League\Flysystem\Filesystem, AdapterInterface), and PHP 7.1–7.3 idioms. Confirm your app's PHP/Symfony before wiring anything.AppKernel::registerBundles() or config/bundles.php depending on your Symfony generation).DependencyInjection/Configuration.php in the bundle — that file defines every swp_storage key. Pair it with Resources/config/services.yml to see which service IDs the config drives (the filesystem service, adapter, and any namer/strategy services).swp_storage:, typically selecting a local adapter and a root directory (e.g. %kernel.project_dir%/var/storage or web/uploads), with an S3/other adapter reserved for production.UploadedFile, write it through the bundle's Flysystem-backed filesystem service, persist only the relative path on your entity, and render it via a route or asset base URL.Where to look first, in order: DependencyInjection/Configuration.php → Resources/config/services.yml → the storage/filesystem classes under the bundle's Storage/ (or equivalent) namespace → Tests/ for the intended usage shape.
Inject the filesystem, never touch the disk directly. The bundle's value is that it hands you a configured League\Flysystem\FilesystemInterface (aliased in the container). Type-hint that interface in your services and use write, writeStream, read, readStream, has, delete, listContents. This keeps local/S3 swap-out a config change rather than a code change.
public function __construct(private FilesystemInterface $filesystem) {}
public function store(UploadedFile $file, string $dir): string
{
$path = sprintf('%s/%s.%s', $dir, bin2hex(random_bytes(8)), $file->guessExtension());
$this->filesystem->writeStream($path, fopen($file->getPathname(), 'rb'));
return $path; // persist this relative path only
}
Separate storage path from public URL. Persist relative paths (articles/images/ab12cd.jpg) in Doctrine. Resolve to a URL at render time through an asset base URL, CDN prefix, or a streaming controller for private files. Never store absolute filesystem paths — they break the moment the adapter changes.
Multiple filesystems per environment. Define a local adapter for dev/test and an object-store adapter for prod, both behind the same interface. Inject an alias per consumer (public media vs. private documents) so a misconfiguration can't leak private files into the public bucket.
Stream, don't buffer. News-media workloads mean large images and PDFs. Use writeStream/readStream and getSize/getMimetype rather than file_get_contents through the adapter. Add a cached adapter (Flysystem's CachedAdapter) if you call listContents in listing screens.
Deterministic rendition naming. If you generate thumbnails, derive names like <path>/<name>_<width>x<height>.<ext> so lookups are pure functions of the original path, and deletion can enumerate derived files by prefix.
Upload validation belongs before storage. Validate mime type, extension, and size in a Symfony form/constraint, then hand the stream to the filesystem. The bundle is a storage layer, not a validator; don't expect it to reject a .php renamed to .jpg.
Wrap it in your own narrow interface. Define App\Storage\StorageInterface with store(), delete(), url() and adapt the bundle's filesystem inside. This is the single most valuable pattern here: it isolates your domain from a 2018 subtree split and makes a future Flysystem 3 migration a one-class change.
Testing. Point the adapter at var/tests/storage (or an in-memory adapter) in config/packages/test/, truncate between tests, and assert on paths rather than bytes. If the bundle ships fixtures/factory services, reuse them instead of hand-rolling uploads.
SuperdeskWebPublisher/SuperdeskWebPublisher monorepo. Any local patch you make will be silently overwritten on the next split sync — fork the monorepo or vendor-patch instead.Definition methods, changed autowiring aliases, FileLocator::locate() signature drift, and Flysystem 1.x vs 3.x API breakage (AdapterInterface was split into FilesystemAdapter/FilesystemOperator). Budget for a rewrite of the wiring, not a tweak.ServiceNotFoundException deep in a compiler pass. Run bin/console debug:container swp_storage and debug:container --tag=<storage tag> to confirm what was actually registered; check the compiled container in var/cache/dev when in doubt... segments. Don't pass absolute paths or Windows-style separators. For the local adapter, set the root as an absolute path (e.g. %kernel.project_dir%/var/storage) — relative roots resolve against the working directory and "work" in CLI but fail under PHP-FPM.var/ or web/uploads must be writable by the web user; containerized setups frequently need an explicit volume plus a matching UID. A silent write() returning false is almost always permissions, not code.postRemove listener or a scheduled command that reconciles DB paths against listContents, including renditions.swp/* together to the same commit/version and commit composer.lock. A lone version bump on swp/storage-bundle will desync from swp/core-bundle and fail at runtime.How can I help you explore Laravel packages today?