[Image] Add the UX Image package - #3768
Open
smnandre wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
So I had the intention to open the Upload PR first ... but #3765 kinda changed my plan :)
I don't think it's a question of "one or the other one", and let's see what feature, DX, etc both PR bring to the table, and what we can build from there.
I'll write a longer description tomorrow, but I didn't want to let too much time pass before opening this.
/!\ Review warning: this is a big one...
TL;DR;
UX Image turns one uploaded raster image into durable responsive-image metadata, then renders native
<picture>and<img>markup without storage I/O or JavaScript.Applications keep ownership of uploads and entities while the bundle provides one explicit processing, storage and rendering contract.
UX Image
Process the image once:
Render the persisted value anywhere:
{{ ux_picture(product.image, { alt: product.name, lazy: false, fetchpriority: 'high', }) }}The browser receives native responsive markup and selects the appropriate format, width and density. Rendering uses the persisted
ImageAssetmetadata only: it does not reopen the source image or contact storage.Objectives
UX Image provides a Symfony-native image pipeline with a deliberately small application boundary:
ImageAssetvalue that remains independent from entities and storage implementations;Pipeline
flowchart LR A["UploadedFile"] --> B["Inspect real bytes"] B --> C["Named profile"] C --> D["Generate variants"] D --> E["Local or Flysystem storage"] E --> F["Immutable ImageAsset"] F --> G["Application persistence"] G --> H["ux_picture() or ux_image()"] H --> I["Native responsive HTML"]Processing and rendering are separate operations. The expensive work happens when the application accepts the image; ordinary page rendering reads only persisted metadata.
Features
ImageAsset, JSON serialization contract and optional Doctrine DBAL type;ux_picture(),ux_image()and an optional Twig Component producesrcset,sizes, intrinsic dimensions, media conditions and loading hints;Out of Scope
These boundaries are intentional. UX Upload, Symfony Forms or application code can provide the input; UX Image starts when the application has an authorized
UploadedFileto process.Requirements
Required Dependencies
The package also uses Symfony Config, Console, DependencyInjection, Filesystem, HttpFoundation and HttpKernel, plus PSR-6 cache contracts.
Optional Dependencies
league/flysystem ^3.0doctrine/dbal ^4.0image_assetJSON persistence typeintervention/image ^3.0intervention/image-driver-vipssymfony/ux-twig-component ^3.0<twig:ux:image>componentOptional integrations are registered only when their concrete dependency is available. The core value objects, renderer contracts and local storage do not require them.
Usage
Define the outputs required by the layout:
Process the real uploaded file after application authorization:
The application persists the returned
ImageAssetwith its owning model. It can use its own mapping or the optional Doctrine DBAL type.Configuration
The default configuration works with local storage and GD. Applications add named profiles and storages as their layouts and deployment require:
Profiles select transformation behavior. Storage is selected at processing time and recorded in the resulting asset, so the same profile can publish to different storage backends.
Processing modes are explicit:
immediatewrites the original and variants before returning;deferredstores an asset that can be completed later;asyncdelegates dispatch to an application implementation ofImageProcessingDispatcherInterface.The bundle does not invent an application message, owner identifier or persistence transaction for asynchronous work.
Rendering
ux_picture()performs format negotiation through<source>elements and keeps a JPEG, PNG or original fallback in<img>.ux_image()renders a single native<img>when format negotiation is unnecessary.Both functions support:
srcset, profile-levelsizesand density descriptors;The optional
<twig:ux:image>component delegates to the same renderer. There is one rendering contract, not a second component-specific implementation.Storage and Persistence
ImageAssetstores paths, dimensions, MIME information, variants, profile name and profile revision. It stores no resolved public URL. This keeps persisted data portable when a CDN hostname or URL strategy changes.Storage publication uses immutable generation keys. A failed processing run removes only the new objects it created; an existing published generation is never overwritten. The application makes the database update durable before deleting an older generation.
Rendering never checks storage existence. The persisted asset and its stored objects therefore form an application consistency boundary.
Security
Images are untrusted binary input. Security is applied before processing, while writing outputs and when generating public markup.
Input Files
Processing and Storage
Authorization and Delivery
The application authorizes the owner and tenant before calling
process(). It must never accept anImageAssetJSON document or storage path directly from a client.Public URL prefixes and CDN builders provide addressing, not access control. Private originals require private storage and an application-owned signed URL adapter or controller.
Regeneration
Regeneration starts from application persistence, never from a storage scan. Applications implement:
ImageAssetProviderInterfaceto expose bounded, stably ordered batches;ImageAssetPersisterInterfaceto publish each replacement with application-level compare-and-swap semantics.php bin/console ux:image:regenerate product \ --storage=default_public \ --batch-size=100The command supports dry runs, current-revision skipping, forced regeneration and resumable opaque cursors. The provider and persister keep entity knowledge and transactions where they belong: in the application.
Tests
Documentation
The documentation follows the image lifecycle from profile design to production operation:
overview.mdinstallation.mdprocessing.mdimage-asset.mdrendering.mdstorage.mdregeneration.mdintegrations.mdsecurity.mdconfiguration.mdtesting.mddebugging.mdarchitecture.mdThe Markdown pages will be converted to reStructuredText before the documentation is submitted for publication.
Coming Next
Separate pull requests can provide: