phil / media-bundle
A media library for Symfony: files and versions on any Flysystem storage, collections, images transformed around their focal point to WebP, AVIF, JPEG or PNG with GD or libvips, tiny previews, purified SVG, responsive Img and Picture components, a file explorer with a picker, a Doctrine column type,
Package info
github.com/parler-haut-interagir-librement/media-bundle
Type:symfony-bundle
pkg:composer/phil/media-bundle
Requires
- php: >=8.5
- ext-fileinfo: *
- ext-gd: *
- ext-mbstring: *
- ext-sodium: *
- doctrine/dbal: ^4.5
- doctrine/orm: ^3.7
- league/flysystem: ^3.30
- psr/clock: ^1.0
- symfony/config: ^8.2
- symfony/console: ^8.2
- symfony/dependency-injection: ^8.2
- symfony/http-foundation: ^8.2
- symfony/http-kernel: ^8.2
- symfony/process: ^8.2
- symfony/routing: ^8.2
- symfony/security-core: ^8.2
- symfony/security-csrf: ^8.2
- symfony/service-contracts: ^3.6
- symfony/uid: ^8.2
Requires (Dev)
- doctrine/doctrine-bundle: ^3.3
- doctrine/persistence: ^4.1
- easycorp/easyadmin-bundle: ^5.8
- league/flysystem-local: ^3.30
- league/flysystem-memory: ^3.29
- phil/browser-security-bundle: ^0.1@dev
- phil/creation-disclosure: ^0.1@dev
- phil/svg-sanitizer: ^0.1@dev
- phpunit/phpunit: ^13.4
- psr/link: ^2.0
- psr/log: ^3.0
- symfony/asset-mapper: ^8.2
- symfony/clock: ^8.2
- symfony/event-dispatcher: ^8.2
- symfony/form: ^8.2
- symfony/framework-bundle: ^8.2
- symfony/options-resolver: ^8.2
- symfony/property-access: ^8.2
- symfony/translation-contracts: ^3.6
- symfony/twig-bridge: ^8.2
- symfony/twig-bundle: ^8.2
- symfony/ux-twig-component: ^3.7
- symfony/validator: ^8.2
- symfony/web-link: ^8.2
- twig/twig: ^3.21
Suggests
- easycorp/easyadmin-bundle: For MediaField, the field of a medium in EasyAdmin
- league/flysystem-aws-s3-v3: To keep files in an S3 bucket
- phil/browser-security-bundle: To serve every file under its policy of user content
- phil/creation-disclosure: To declare how a medium was made as an IPTC digital source type
- phil/svg-sanitizer: To purify and minify SVG images before they are kept
- symfony/asset-mapper: To serve the explorer's Stimulus controllers without a build step
- symfony/form: For MediaPickerType, the field that opens the picker
- symfony/ux-twig-component: For the Img and Picture components
- symfony/validator: For the MediaFile constraint
- symfony/web-link: To preload the main image of a page, with priority
- twig/twig: For media_url(), media_schema(), the explorer and the components
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-12 23:53:14 UTC
README
Uploaded files and images, described, versioned and served at signed URLs in the formats you declare.
phil/media-bundle is a media library for a Symfony application. Files are kept in a directory or on any Flysystem
filesystem, an S3 bucket among them, and described in Doctrine: their versions, their collection, a title, alternative
text and a credit, a license, and what they owe to artificial intelligence. Images are rendered on demand to the formats
the application declares — transformations in a row, cropping around a focal point, resizing, expanding, and narrower
widths for a srcset —, never enlarged unless told. They come out as WebP, AVIF, JPEG or PNG, drawn from the original
each time by GD, through cwebp and avifenc when they are installed, or by libvips. Each version of an image may get
a tiny preview, painted behind the image while it loads by a Stimulus controller, without a style attribute that a
strict Content Security Policy would drop, and phil:media:placeholders gives one to the images kept before; an SVG
image is purified and made smaller before it is kept, when phil/svg-sanitizer is installed, and served as an image.
The Img and Picture components print them sized, lazy and with a srcset, or fetched first and preloaded for the
main image of a page; a file explorer, behind a role or a voter and Symfony's stateless CSRF tokens, uploads, renames, moves
and deletes them, opens as a picker for a form field or an EasyAdmin field, and is linked from an EasyAdmin menu; a
phil_media column lets any entity
point at a medium, checked by a constraint. The bundle does not decide how the AI disclosure is shown to visitors, and
deleting a medium removes it for good: moving it to a trash is the application's choice, with phil/trash-bundle.
It is for a site where editors upload pictures and documents that pages then show, and where those pages must stay fast and light on a phone. Replacing a file keeps the medium's identifier, so every page that shows it shows the new file, while the previous file stays recorded as a version. Alternative text, credit and license sit on the medium itself, which makes accessible and properly credited images the easy path. A record that still shows a medium decides what its deletion does: refuse it, lose the medium, or keep its identifier.
The bundle plugs into Doctrine ORM for its records, into Symfony's routing and HTTP kernel for MediaController, and
into Twig for media_url(), media_schema() and the explorer when Twig is installed, and into Twig components, the
form and validator components, AssetMapper, EasyAdmin and the profiler when they are. Storage and image processing are
ports, MediaStorage and ImageProcessor, with a directory, a Flysystem, a GD and a libvips adapter. With
phil/creation-disclosure, a medium declares how it was made as an IPTC digital source type; with
phil/browser-security-bundle, every file is served under its policy of user content. Each URL carries the medium's
version and an HMAC-SHA256 signature of its path, so a response never changes and a browser or a CDN may keep it a year.
With url_ttl, the signature also covers the moment the URL expires, and the response is kept no longer. A URL the
application did not hand out answers 404, and a file that could run a script in the browser, such as an HTML page, is
downloaded rather than shown; a purified SVG image shows in an <img>, and is saved when opened directly.
Status
| Part | State |
|---|---|
Media, MediaVersion, MediaCollection, FileInfo, AiDisclosure |
Implemented and tested |
MediaLibrary: upload, replace, delete, render; ImageFormat, RenderBox |
Implemented and tested |
| Transformations: thumbnail, crop, resize, widen, heighten, expand; narrower widths | Implemented and tested |
FilesystemStorage, FlysystemStorage, GdImageProcessor, BinaryEncoder, DoctrineMediaRepository |
Implemented and tested |
Signed URLs: UrlSigner, MediaController, media_url(), media_schema() |
Implemented and tested |
Img and Picture components, ResponsiveImages; priority, preloaded with symfony/web-link |
Implemented and tested |
The phil_media column type, MediaFile, deletion behaviours |
Implemented and tested |
The file explorer, MediaPickerType, the Stimulus controllers |
Implemented and tested; the controllers checked by hand |
The EasyAdmin field, MediaField; the menu link to the explorer, MediaAdmin |
Implemented and tested |
| The profiler panel | Implemented and tested |
VipsImageProcessor, ImageProcessors: libvips through its command-line tools |
Implemented and tested against stand-ins of the tools; rendered by libvips where it is installed |
Placeholders: a tiny WebP preview per version, the placeholder Stimulus controller, phil:media:placeholders |
Implemented and tested; the controller's painting not checked in a browser |
SVG images purified and minified: SanitizingSvgPurifier, SvgMinifier; served as image/svg+xml |
Implemented and tested |
The policy of user content of phil/browser-security-bundle on every file served |
Implemented and tested |
MediaRepository::getMany(): several media in one query |
Implemented and tested |
How a medium was made, as an IPTC digital source type of phil/creation-disclosure |
Implemented and tested |
Documentation
| Document | What it covers |
|---|---|
| Installation | Requirements, registering, configuration, routes, tables. |
| The library | Uploading, replacing, describing, licensing, collections, deleting. |
| Formats and URLs | Formats and their transformations, the focal point, rendering and encoders, signed URLs, caching. |
| Storage | The directory, Flysystem filesystems — local, S3 —, a storage of the application's own. |
| Responsive images | The Img and Picture components: sizes, srcset, file types, fallbacks, priority, placeholders. |
| Records that show media | The phil_media column type, the MediaFile constraint, what a deletion does. |
| The file explorer | Routes, listing, uploads, the Stimulus controllers, the picker and its form field. |
| EasyAdmin | MediaField: a medium in a CRUD controller, with the picker in its form; the explorer in the menu. |
| The profiler panel | What a request rendered and read, in debug. |
| Exceptions | What the bundle throws, and when. |
| Extension points | What an application implements, configures, replaces or relies on. |
| Testing | How the bundle is tested. |
| Reference | Every type of the bundle. |
Quick start
$media = $library->upload($uploadedFile->getPathname(), $uploadedFile->getClientOriginalName(), $collection); $media->describe('The party', alt: 'People dancing under paper lanterns', credit: 'Ada'); $media->license('CC-BY-4.0', AiDisclosure::Assisted); $media->focus(0.3, 0.4); $repository->save($media);
<twig:PhilMedia:Picture :media="post.cover" format="card" sizes="(min-width: 60em) 50vw, 100vw"/> <twig:PhilMedia:Img :media="page.hero" format="hero" sizes="100vw" priority/> <img src="{{ media_url(media, 'thumbnail', 'avif') }}" alt="{{ media.alt }}">
Requirements
PHP 8.5 or later with ext-fileinfo, ext-gd, ext-mbstring and ext-sodium; Doctrine ORM 3.7; Flysystem 3;
Symfony 8.2's console, dependency injection, config, HTTP foundation and kernel, process, routing, service contracts
and UID; PSR-20's clock. Twig, Twig components, the form and validator components, AssetMapper, WebLink, EasyAdmin,
phil/browser-security-bundle, phil/creation-disclosure, phil/svg-sanitizer and libvips are optional.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.