contenir / contenir-storage
Framework-agnostic asset storage for Contenir CMS — local, S3-compatible, and Cloudflare Images adapters with a shared variant pipeline.
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- league/flysystem: ^3.29
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- infection/infection: ^0.34.1
- league/flysystem-aws-s3-v3: ^3.29
- league/flysystem-memory: ^3.29
- league/mime-type-detection: ^1.16
- php-db/phpdb-qa-tools: 0.1.x-dev
- phpunit/phpunit: ^11.5.42
Suggests
- ext-imagick: Preferred by ImageResizer for variant generation; the magick/convert CLI is used otherwise.
- league/flysystem-aws-s3-v3: Required by StorageConfig to build s3 and cloudflare-images backends.
Provides
None
Conflicts
None
Replaces
- contenir/storage: v2.2.0
- dev-main / 2.2.x-dev
- v2.2.0
- v2.1.1
- v2.1.0
- v2.0.0
- 0.x-dev
- v0.6.6
- v0.6.5
- v0.6.4
- v0.6.3
- v0.6.2
- v0.6.1
- v0.6.0
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.1
- v0.4.0
- v0.3.6
- v0.3.5
- v0.3.4
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.0
- dev-chore/tidy
- dev-chore/rename-package
- dev-chore/remove-dead-code
- dev-fix/s3-temp-cleanup
- dev-ci/infection
- dev-ci/contenir-qa-tools
- dev-v2/qa-tools
This package is auto-updated.
Last update: 2026-10-06 00:51:44 UTC
README
Formerly contenir/storage; the old package is abandoned in favour of this one.
Framework-agnostic asset storage for Contenir CMS.
One StorageInterface for storing, listing, renaming and deleting CMS assets
on a local filesystem, an S3-compatible bucket (AWS S3, Cloudflare R2, MinIO)
or behind Cloudflare's URL image transforms, with a shared pipeline of named
image variants (thumbnails, responsive ladders, AVIF/WebP siblings).
- Backends:
LocalFilesystem,S3,CloudflareImages, andInMemoryStoragefor tests. - Variants:
Variant,VariantRegistry, art-directedVariantProfileladders and per-path ownership throughPathVariantResolver. - Uploads:
UploadInputfrom$_FILESor PSR-7, named and typed from the detected bytes byDefaultUploadResolver. - Configuration:
StorageConfigbuilds aStorageManagerfrom one flatstoragearray.
Requirements
- PHP 8.3, 8.4 or 8.5
league/flysystem3.29 or later- ImageMagick, through the
imagickextension or themagick/convertCLI, for variant generation league/flysystem-aws-s3-v3for thes3andcloudflare-imagesbackends
Install
composer require contenir/contenir-storage
Add league/flysystem-aws-s3-v3 when you use an S3-compatible bucket:
composer require league/flysystem-aws-s3-v3
The 0.x releases, which support PHP 8.1, remain available from the 0.x
branch and v0.* tags; see UPGRADE-2.0.md.
Usage
From configuration
use Contenir\Storage\Config\StorageConfig; use Contenir\Storage\Image\ImageResizer; use Contenir\Storage\UploadInput; $manager = StorageConfig::fromArray($config['storage'] ?? null, new ImageResizer(), '/var/www/public'); $storage = $manager->primary(); $entry = $storage->store(UploadInput::fromFilesArray($_FILES['image']), 'asset/library/news'); $storage->url($entry->path); // "/asset/library/news/photo.jpg" $storage->url($entry->path, 'admin-thumb'); // variant URL, or null when not materialised $storage->thumbnailUrl($entry->path); // the same, null when the variant is undeclared $storage->variantUrls($entry->path, 'card-480'); // ['avif' => …, 'source' => …], no existence checks
By hand
use Contenir\Storage\Adapter\LocalFilesystem; use Contenir\Storage\Image\ImageResizer; use Contenir\Storage\StorageManager; use Contenir\Storage\Variant; use Contenir\Storage\VariantFit; use Contenir\Storage\VariantRegistry; $variants = new VariantRegistry( new Variant('admin-thumb', 180, 180, VariantFit::Contain), new Variant('card', 600, 400, VariantFit::Cover, formats: ['avif', 'webp'], quality: 80), ); $manager = new StorageManager(); $manager->register('local', new LocalFilesystem( rootPath: '/var/www/public', publicPath: '', variants: $variants, resizer: new ImageResizer(), ), isPrimary: true);
The storage contract
| Method | Purpose |
|---|---|
store(UploadInput, string $directory): Entry |
Save an upload under a detected-type name and generate its variants |
url(string $path, ?string $variant = null): ?string |
Public URL, or null when the asset or variant is missing |
thumbnailUrl(string $path): ?string |
url($path, 'admin-thumb'), null when the variant is not declared |
urlsForKey(string $path): list<string> |
Every URL a CDN might cache for the asset (for purges) |
variantUrls(string $path, string $variant): array<string, string> |
Per-format variant URLs, built without I/O |
list(string $path, ?ListOptions): iterable<Entry> |
Browse a directory, filtered and sorted |
exists(string $path): bool |
Whether the asset exists |
delete(string $path): void |
Delete the asset and every variant sibling |
rename(string $from, string $to): void |
Move the asset and its variant siblings |
imageMeta(string $path): ImageMeta |
Width, height and MIME of an image |
regenerateMissingVariants(string $path): list<string> |
Backfill variants the asset is missing |
Optional capabilities are separate interfaces: MissingVariantsReporterInterface
(audit without writing), BulkDeleteInterface (delete an exact key list) and
OnDemandVariantGeneratorInterface (materialise one variant from its key).
Documentation
- Backends: local, S3, Cloudflare Images and in-memory
- Configuration: the
storagearray andStorageConfig - Variants: variants, profiles, formats and path ownership
- Uploads:
UploadInput, naming, type detection,PathResolver - Image resizing:
ImageResizerand its test double - Backfill and bulk operations
- Asset naming and provenance spec
Development
The QA toolchain is php-db/phpdb-qa-tools.
Mago is a standalone binary, installed
separately (brew install mago).
composer check # everything below composer cs-check # mago format --check && mago lint composer static-analysis # mago analyze composer test # unit suite: no I/O, the bucket is an in-memory Flysystem composer test-integration # integration suite: real filesystem and ImageMagick in a temp directory composer test-coverage # both suites, clover.xml for Codecov composer mutation-test # Infection over both suites (needs pcov or Xdebug)
No test talks to a real cloud service. S3 behaviour is exercised against an
in-memory Flysystem double (tests/TestAsset/Flysystem/FailingFilesystem).
License
MIT. See LICENSE.