unite-cms/storage-bundle
A Laravel storage bundle for Unite CMS that provides a flexible layer for managing files and media within your CMS. It integrates storage configuration and file handling into the Unite CMS ecosystem, helping you organize and serve uploaded assets in PHP applications.
This bundle is the storage layer for Unite CMS 1.x-style content types — it registers file and image field types plus the admin views for uploading/previewing them, backed by an S3 (or S3-compatible) adapter. It is a leaf dependency: it does nothing on its own and only makes sense inside an existing Unite CMS installation.
Minimal steps:
composer require unite-cms/storage-bundle — expect Composer to resolve against an old Symfony/LTS branch and an old league/flysystem (v1 era). If you are on Symfony 4.4+, you will likely need --with-all-dependencies and may have to pin unite-cms/core-bundle to the matching minor.config/bundles.php (Symfony 4) or AppKernel.php (Symfony 3) after UniteCMS\CoreBundle\UniteCMSCoreBundle.unite_storage: (see DependencyInjection/Configuration.php — this is the authoritative list of keys; the README on a 2018-era repo is usually stale).bin/console cache:clear and check debug:container | grep -i storage to confirm unite.storage.adapter (name varies by version) is wired to the S3 service rather than the null/local one.# config/types/article.yml
Article:
fields:
title: { type: text }
teaser: { type: image }
pdf: { type: file }
Then open the admin UI, create an Article, upload a JPEG, save, and query it over GraphQL to confirm the URL round-trips. That single flow exercises config, adapter, upload validation, and URL generation — if it passes, everything else is variations.
Where to look first, in order: FieldType/ (what the GraphQL schema exposes per field), Storage/ (the adapter contract and the S3 implementation), Resources/config/services.yml (service ids and the S3 client wiring), and only then the README.
Field-type driven, not controller driven. You almost never call storage code directly. Day-to-day work is declaring file/image fields in content-type YAML/annotations and consuming the resulting GraphQL type. The value shape is typically { id, name, size, mimeType, url } for files and a variant with width/height plus thumbnail sizes for images — always introspect with the GraphQL playground (__type(name: "ArticleTeaser")) instead of guessing, since these keys shifted between releases.
Single field, multiple render contexts. Because the field returns both metadata and a URL, the common pattern is:
query {
findArticle(limit: 1) {
teaser { url name size mimeType }
}
}
…and then let the front end pick a size. Don't add a second image field per size — use one field and let the image-derivative configuration (if enabled in your version) or a CDN/Imgix layer handle resizing. Duplicating fields is the fastest way to blow past S3 object-count sanity.
Adapter swapping for environments. The adapter is a service, so the standard workflow is: real S3 bucket in prod, a local-disk or MinIO adapter in dev/test. Do this through Symfony's when@dev config blocks rather than editing code. Keep fixtures deterministic — a shared dev bucket means tests start failing the moment someone deletes a file.
S3-compatible endpoints (MinIO, DigitalOcean Spaces, R2). Most of the code paths work if you can inject a custom S3Client with endpoint and use_path_style_endpoint. The clean integration point is to define your own Aws\S3\S3Client service and alias the bundle's expected client id to it, rather than forking the bundle.
Orphan cleanup as an explicit job. The bundle will upload and delete-on-field-clear, but content deletion at the CMS level historically does not cascade reliably to S3. The reliable pattern is a nightly command that lists bucket keys, lists file references from content via GraphQL, and diffs — run it in dry-run for a week before enabling --delete.
Admin views for custom fields. If you build a field type that wraps storage (e.g. a gallery of images), copy the bundle's admin view templates under Resources/views/ and register your type alongside them rather than overriding the bundle's, which makes upgrades (however rare) tractable.
composer.json ("dev-master#<sha>" or a VCS repository entry), and budget for a Symfony-major upgrade being entirely on you. Do not assume a security fix will land.League\Flysystem\AwsS3v3\AwsS3Adapter / Filesystem API is pre-v2. If your project already pulls Flysystem 2/3 for something else, you will get a class-not-found at container compile time, not at runtime — read the stack trace from cache:clear, not from the browser.Aws\Exception\S3Exception: Access Denied on upload usually means the IAM user lacks s3:PutObject and s3:PutObjectAcl; on read it usually means the bucket has Block Public Access enabled. Check these before touching bundle config.PermanentRedirect / AuthorizationHeaderMalformed rather than a clean 403. Set region explicitly; never rely on bucket auto-detection.upload_max_filesize / post_max_size, not the bundle's validation. post_max_size being exceeded also empties $_POST, which presents as "the form just resets."CacheControl: max-age=31536000, immutable at upload time — retrofitting via a bucket-wide Lambda is painful.debug:container for the adapter service, (2) bin/console debug:config unite_storage for resolved config, (3) tail the S3 request with AWS_DEBUG=1-style logging on the client, (4) test the raw GraphQL mutation in the playground to separate UI problems from storage problems.S3Client service, custom field type extending the bundled one, custom admin view template. Not safe: decorating internals in Storage/ — the interfaces are thin and changed between minor releases in this era.How can I help you explore Laravel packages today?